voidmachine 0.0.0 → 4.0.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/LICENSE +21 -0
- package/README.md +123 -4
- package/bin/void-harness.mjs +12 -0
- package/bin/void-machine.mjs +12 -0
- package/core-assets/.claude-plugin/plugin.json +253 -0
- package/core-assets/.gitkeep +0 -0
- package/core-assets/PHILOSOPHY.md +218 -0
- package/core-assets/PROJECT-DOCTRINE.template.md +14 -0
- package/core-assets/adapters/security/manifest.yaml +96 -0
- package/core-assets/agents/.gitkeep +0 -0
- package/core-assets/agents/accessibility-specialist.md +59 -0
- package/core-assets/agents/api-integration-engineer.md +59 -0
- package/core-assets/agents/code-explorer.md +94 -0
- package/core-assets/agents/code-explorer.source +7 -0
- package/core-assets/agents/data-migration-engineer.md +59 -0
- package/core-assets/agents/devex-docs-engineer.md +57 -0
- package/core-assets/agents/doctrine-critic.md +91 -0
- package/core-assets/agents/doctrine-critic.source +11 -0
- package/core-assets/agents/domain-architect.md +56 -0
- package/core-assets/agents/experience-designer.md +59 -0
- package/core-assets/agents/frontend-engineer.md +60 -0
- package/core-assets/agents/independent-code-reviewer.md +61 -0
- package/core-assets/agents/migration-planner.md +97 -0
- package/core-assets/agents/migration-planner.source +7 -0
- package/core-assets/agents/observability-sre-engineer.md +59 -0
- package/core-assets/agents/pdf-specialist.md +58 -0
- package/core-assets/agents/performance-engineer.md +58 -0
- package/core-assets/agents/product-challenger.md +55 -0
- package/core-assets/agents/security-engineer.md +66 -0
- package/core-assets/agents/silent-failure-hunter.md +94 -0
- package/core-assets/agents/silent-failure-hunter.source +8 -0
- package/core-assets/agents/solution-architect.md +57 -0
- package/core-assets/agents/test-qa-engineer.md +60 -0
- package/core-assets/agents/type-design-analyzer.md +93 -0
- package/core-assets/agents/type-design-analyzer.source +8 -0
- package/core-assets/agents/visual-craft-director.md +62 -0
- package/core-assets/codex/hooks.json +157 -0
- package/core-assets/contract-history/visual-craft-director/v2.yaml +43 -0
- package/core-assets/data/catalog.v3.json +10479 -0
- package/core-assets/data/certification.json +1800 -0
- package/core-assets/data/identity.json +8 -0
- package/core-assets/data/model.json +4226 -0
- package/core-assets/enforce/ci-enforce.sh +183 -0
- package/core-assets/hooks/_checks.sh +145 -0
- package/core-assets/hooks/_hooklib.sh +163 -0
- package/core-assets/hooks/_syntax-worker.cjs +503 -0
- package/core-assets/hooks/_void-hook.mjs +5459 -0
- package/core-assets/hooks/activation-meter.sh +13 -0
- package/core-assets/hooks/anti-ai-slop-grep.sh +38 -0
- package/core-assets/hooks/auto-format.sh +9 -0
- package/core-assets/hooks/axe-precommit.sh +35 -0
- package/core-assets/hooks/block-dangerous-bash.sh +10 -0
- package/core-assets/hooks/block-protected-push.sh +81 -0
- package/core-assets/hooks/boundary-direction-check.sh +10 -0
- package/core-assets/hooks/control-character-grep.sh +10 -0
- package/core-assets/hooks/fowler-refactors.txt +71 -0
- package/core-assets/hooks/large-cl-grep.sh +9 -0
- package/core-assets/hooks/llm-cost-precommit.sh +55 -0
- package/core-assets/hooks/migration-lint.sh +58 -0
- package/core-assets/hooks/migration-pr-template.sh +55 -0
- package/core-assets/hooks/no-ai-design-slop.sh +10 -0
- package/core-assets/hooks/no-any-grep.sh +10 -0
- package/core-assets/hooks/no-as-cast-grep.sh +10 -0
- package/core-assets/hooks/no-console-log-grep.sh +10 -0
- package/core-assets/hooks/no-null-grep.sh +10 -0
- package/core-assets/hooks/no-only-no-skip.sh +10 -0
- package/core-assets/hooks/outcome-meter.sh +13 -0
- package/core-assets/hooks/pre-PR-review-evidence.sh +47 -0
- package/core-assets/hooks/protect-sensitive-files.sh +10 -0
- package/core-assets/hooks/refactor-named-grep.sh +49 -0
- package/core-assets/hooks/secret-in-content.sh +10 -0
- package/core-assets/hooks/sessionstart-context.sh +9 -0
- package/core-assets/hooks/stop-typecheck.sh +9 -0
- package/core-assets/hooks/tdd-guard.md +90 -0
- package/core-assets/hooks/tdd-guard.sh +10 -0
- package/core-assets/hooks/test-name-lint.sh +10 -0
- package/core-assets/hooks/tidying-commit-prefix.sh +37 -0
- package/core-assets/hooks/trim-large-output.sh +9 -0
- package/core-assets/hooks/tsc-noemit-precommit.sh +22 -0
- package/core-assets/hooks/ubiquitous-language-lint.sh +58 -0
- package/core-assets/hooks/viewport-screenshot-gate.sh +48 -0
- package/core-assets/modules/.gitkeep +0 -0
- package/core-assets/packs/pack-mobile/skills/void-eas-build-profile/.source +7 -0
- package/core-assets/packs/pack-mobile/skills/void-eas-build-profile/SKILL.md +164 -0
- package/core-assets/packs/pack-mobile/skills/void-eas-build-profile/harness.yaml +14 -0
- package/core-assets/packs/pack-mobile/skills/void-expo-config-plugins/.source +7 -0
- package/core-assets/packs/pack-mobile/skills/void-expo-config-plugins/SKILL.md +178 -0
- package/core-assets/packs/pack-mobile/skills/void-expo-config-plugins/harness.yaml +14 -0
- package/core-assets/packs/pack-mobile/skills/void-expo-router-pattern/.source +8 -0
- package/core-assets/packs/pack-mobile/skills/void-expo-router-pattern/SKILL.md +209 -0
- package/core-assets/packs/pack-mobile/skills/void-expo-router-pattern/harness.yaml +14 -0
- package/core-assets/packs/pack-mobile/skills/void-ota-update-strategy/.source +7 -0
- package/core-assets/packs/pack-mobile/skills/void-ota-update-strategy/SKILL.md +194 -0
- package/core-assets/packs/pack-mobile/skills/void-ota-update-strategy/harness.yaml +14 -0
- package/core-assets/packs/pack-monorepo/skills/void-dependency-direction/.source +5 -0
- package/core-assets/packs/pack-monorepo/skills/void-dependency-direction/SKILL.md +160 -0
- package/core-assets/packs/pack-monorepo/skills/void-dependency-direction/harness.yaml +14 -0
- package/core-assets/packs/pack-monorepo/skills/void-package-extraction/.source +5 -0
- package/core-assets/packs/pack-monorepo/skills/void-package-extraction/SKILL.md +70 -0
- package/core-assets/packs/pack-monorepo/skills/void-package-extraction/harness.yaml +14 -0
- package/core-assets/packs/pack-monorepo/skills/void-service-package/.source +5 -0
- package/core-assets/packs/pack-monorepo/skills/void-service-package/SKILL.md +122 -0
- package/core-assets/packs/pack-monorepo/skills/void-service-package/harness.yaml +14 -0
- package/core-assets/packs/pack-monorepo/skills/void-turbo-pipeline-tuning/.source +7 -0
- package/core-assets/packs/pack-monorepo/skills/void-turbo-pipeline-tuning/SKILL.md +116 -0
- package/core-assets/packs/pack-monorepo/skills/void-turbo-pipeline-tuning/harness.yaml +14 -0
- package/core-assets/packs/pack-nextjs/skills/void-cache-component-pattern/.source +5 -0
- package/core-assets/packs/pack-nextjs/skills/void-cache-component-pattern/SKILL.md +162 -0
- package/core-assets/packs/pack-nextjs/skills/void-cache-component-pattern/harness.yaml +14 -0
- package/core-assets/packs/pack-nextjs/skills/void-instrumentation-setup/.source +5 -0
- package/core-assets/packs/pack-nextjs/skills/void-instrumentation-setup/SKILL.md +161 -0
- package/core-assets/packs/pack-nextjs/skills/void-instrumentation-setup/harness.yaml +14 -0
- package/core-assets/packs/pack-nextjs/skills/void-loading-error-boundaries/.source +5 -0
- package/core-assets/packs/pack-nextjs/skills/void-loading-error-boundaries/SKILL.md +176 -0
- package/core-assets/packs/pack-nextjs/skills/void-loading-error-boundaries/harness.yaml +14 -0
- package/core-assets/packs/pack-nextjs/skills/void-parallel-routes-slots/.source +5 -0
- package/core-assets/packs/pack-nextjs/skills/void-parallel-routes-slots/SKILL.md +129 -0
- package/core-assets/packs/pack-nextjs/skills/void-parallel-routes-slots/harness.yaml +14 -0
- package/core-assets/packs/pack-nextjs/skills/void-route-group-decision/.source +5 -0
- package/core-assets/packs/pack-nextjs/skills/void-route-group-decision/SKILL.md +107 -0
- package/core-assets/packs/pack-nextjs/skills/void-route-group-decision/harness.yaml +14 -0
- package/core-assets/packs/pack-pwa/skills/void-install-prompt-ux/.source +5 -0
- package/core-assets/packs/pack-pwa/skills/void-install-prompt-ux/SKILL.md +153 -0
- package/core-assets/packs/pack-pwa/skills/void-install-prompt-ux/harness.yaml +14 -0
- package/core-assets/packs/pack-pwa/skills/void-manifest-checklist/.source +5 -0
- package/core-assets/packs/pack-pwa/skills/void-manifest-checklist/SKILL.md +204 -0
- package/core-assets/packs/pack-pwa/skills/void-manifest-checklist/harness.yaml +14 -0
- package/core-assets/packs/pack-pwa/skills/void-offline-first-mutation/.source +8 -0
- package/core-assets/packs/pack-pwa/skills/void-offline-first-mutation/SKILL.md +266 -0
- package/core-assets/packs/pack-pwa/skills/void-offline-first-mutation/harness.yaml +14 -0
- package/core-assets/packs/pack-pwa/skills/void-service-worker-strategy/.source +5 -0
- package/core-assets/packs/pack-pwa/skills/void-service-worker-strategy/SKILL.md +178 -0
- package/core-assets/packs/pack-pwa/skills/void-service-worker-strategy/harness.yaml +14 -0
- package/core-assets/packs/pack-react/skills/void-accessibility-check/.source +8 -0
- package/core-assets/packs/pack-react/skills/void-accessibility-check/SKILL.md +136 -0
- package/core-assets/packs/pack-react/skills/void-accessibility-check/harness.yaml +14 -0
- package/core-assets/packs/pack-react/skills/void-client-vs-server-component/.source +5 -0
- package/core-assets/packs/pack-react/skills/void-client-vs-server-component/SKILL.md +116 -0
- package/core-assets/packs/pack-react/skills/void-client-vs-server-component/harness.yaml +14 -0
- package/core-assets/packs/pack-react/skills/void-form-pattern/.source +7 -0
- package/core-assets/packs/pack-react/skills/void-form-pattern/SKILL.md +157 -0
- package/core-assets/packs/pack-react/skills/void-form-pattern/harness.yaml +14 -0
- package/core-assets/packs/pack-react/skills/void-state-architecture/.source +5 -0
- package/core-assets/packs/pack-react/skills/void-state-architecture/SKILL.md +160 -0
- package/core-assets/packs/pack-react/skills/void-state-architecture/harness.yaml +14 -0
- package/core-assets/packs/pack-server/skills/void-background-job-pattern/.source +6 -0
- package/core-assets/packs/pack-server/skills/void-background-job-pattern/SKILL.md +214 -0
- package/core-assets/packs/pack-server/skills/void-background-job-pattern/harness.yaml +14 -0
- package/core-assets/packs/pack-server/skills/void-drizzle-migration-safe/.source +6 -0
- package/core-assets/packs/pack-server/skills/void-drizzle-migration-safe/SKILL.md +168 -0
- package/core-assets/packs/pack-server/skills/void-drizzle-migration-safe/harness.yaml +14 -0
- package/core-assets/packs/pack-server/skills/void-env-validation/.source +6 -0
- package/core-assets/packs/pack-server/skills/void-env-validation/SKILL.md +131 -0
- package/core-assets/packs/pack-server/skills/void-env-validation/harness.yaml +14 -0
- package/core-assets/packs/pack-server/skills/void-rate-limit-strategy/.source +6 -0
- package/core-assets/packs/pack-server/skills/void-rate-limit-strategy/SKILL.md +149 -0
- package/core-assets/packs/pack-server/skills/void-rate-limit-strategy/harness.yaml +14 -0
- package/core-assets/packs/pack-server/skills/void-server-action/.source +7 -0
- package/core-assets/packs/pack-server/skills/void-server-action/SKILL.md +181 -0
- package/core-assets/packs/pack-server/skills/void-server-action/harness.yaml +14 -0
- package/core-assets/packs/pack-server/skills/void-testing-server-modules/.source +7 -0
- package/core-assets/packs/pack-server/skills/void-testing-server-modules/SKILL.md +73 -0
- package/core-assets/packs/pack-server/skills/void-testing-server-modules/harness.yaml +14 -0
- package/core-assets/packs/pack-server/skills/void-webhook-handler-pattern/.source +7 -0
- package/core-assets/packs/pack-server/skills/void-webhook-handler-pattern/SKILL.md +205 -0
- package/core-assets/packs/pack-server/skills/void-webhook-handler-pattern/harness.yaml +14 -0
- package/core-assets/policies/core.yaml +95 -0
- package/core-assets/policies/ui.yaml +23 -0
- package/core-assets/profiles/base.yaml +29 -0
- package/core-assets/profiles/expo-config.yaml +36 -0
- package/core-assets/profiles/expo.yaml +35 -0
- package/core-assets/profiles/monorepo.yaml +33 -0
- package/core-assets/profiles/nextjs-config.yaml +31 -0
- package/core-assets/profiles/nextjs.yaml +31 -0
- package/core-assets/profiles/node-server.yaml +31 -0
- package/core-assets/profiles/pwa.yaml +33 -0
- package/core-assets/profiles/react.yaml +39 -0
- package/core-assets/profiles/sql.yaml +39 -0
- package/core-assets/profiles/typescript.yaml +39 -0
- package/core-assets/skills/.gitkeep +0 -0
- package/core-assets/skills/void-accessibility/.source +15 -0
- package/core-assets/skills/void-accessibility/SKILL.md +275 -0
- package/core-assets/skills/void-accessibility/harness.yaml +17 -0
- package/core-assets/skills/void-api-and-interface-design/.source +13 -0
- package/core-assets/skills/void-api-and-interface-design/SKILL.md +231 -0
- package/core-assets/skills/void-api-and-interface-design/harness.yaml +16 -0
- package/core-assets/skills/void-async-safety/.source +13 -0
- package/core-assets/skills/void-async-safety/SKILL.md +287 -0
- package/core-assets/skills/void-async-safety/harness.yaml +16 -0
- package/core-assets/skills/void-audit/.source +15 -0
- package/core-assets/skills/void-audit/SKILL.md +83 -0
- package/core-assets/skills/void-audit/harness.yaml +14 -0
- package/core-assets/skills/void-autopilot/.source +21 -0
- package/core-assets/skills/void-autopilot/SKILL.md +358 -0
- package/core-assets/skills/void-autopilot/harness.yaml +14 -0
- package/core-assets/skills/void-brainstorm/.source +14 -0
- package/core-assets/skills/void-brainstorm/SKILL.md +271 -0
- package/core-assets/skills/void-brainstorm/harness.yaml +14 -0
- package/core-assets/skills/void-checkpoint/.source +39 -0
- package/core-assets/skills/void-checkpoint/SKILL.md +227 -0
- package/core-assets/skills/void-checkpoint/harness.yaml +14 -0
- package/core-assets/skills/void-claude-md/.source +15 -0
- package/core-assets/skills/void-claude-md/SKILL.md +180 -0
- package/core-assets/skills/void-claude-md/harness.yaml +14 -0
- package/core-assets/skills/void-code-review/.source +14 -0
- package/core-assets/skills/void-code-review/SKILL.md +232 -0
- package/core-assets/skills/void-code-review/harness.yaml +15 -0
- package/core-assets/skills/void-commit-discipline/.source +9 -0
- package/core-assets/skills/void-commit-discipline/SKILL.md +211 -0
- package/core-assets/skills/void-commit-discipline/harness.yaml +15 -0
- package/core-assets/skills/void-context/.source +15 -0
- package/core-assets/skills/void-context/SKILL.md +167 -0
- package/core-assets/skills/void-context/harness.yaml +14 -0
- package/core-assets/skills/void-debug/.source +11 -0
- package/core-assets/skills/void-debug/SKILL.md +211 -0
- package/core-assets/skills/void-debug/harness.yaml +14 -0
- package/core-assets/skills/void-decide/.source +9 -0
- package/core-assets/skills/void-decide/SKILL.md +147 -0
- package/core-assets/skills/void-decide/harness.yaml +14 -0
- package/core-assets/skills/void-devex-audit/.source +8 -0
- package/core-assets/skills/void-devex-audit/SKILL.md +113 -0
- package/core-assets/skills/void-devex-audit/harness.yaml +15 -0
- package/core-assets/skills/void-doctor/.source +15 -0
- package/core-assets/skills/void-doctor/SKILL.md +83 -0
- package/core-assets/skills/void-doctor/harness.yaml +14 -0
- package/core-assets/skills/void-domain-driven-design/.source +13 -0
- package/core-assets/skills/void-domain-driven-design/SKILL.md +292 -0
- package/core-assets/skills/void-domain-driven-design/harness.yaml +15 -0
- package/core-assets/skills/void-frontend-design/.source +19 -0
- package/core-assets/skills/void-frontend-design/SKILL.md +274 -0
- package/core-assets/skills/void-frontend-design/harness.yaml +17 -0
- package/core-assets/skills/void-functional/.source +14 -0
- package/core-assets/skills/void-functional/SKILL.md +312 -0
- package/core-assets/skills/void-functional/harness.yaml +15 -0
- package/core-assets/skills/void-graph/.source +15 -0
- package/core-assets/skills/void-graph/SKILL.md +80 -0
- package/core-assets/skills/void-graph/harness.yaml +14 -0
- package/core-assets/skills/void-hexagonal-architecture/.source +12 -0
- package/core-assets/skills/void-hexagonal-architecture/SKILL.md +276 -0
- package/core-assets/skills/void-hexagonal-architecture/harness.yaml +15 -0
- package/core-assets/skills/void-implement/.source +39 -0
- package/core-assets/skills/void-implement/SKILL.md +183 -0
- package/core-assets/skills/void-implement/harness.yaml +14 -0
- package/core-assets/skills/void-learn/.source +13 -0
- package/core-assets/skills/void-learn/SKILL.md +226 -0
- package/core-assets/skills/void-learn/harness.yaml +14 -0
- package/core-assets/skills/void-llm-cost-discipline/.source +13 -0
- package/core-assets/skills/void-llm-cost-discipline/SKILL.md +299 -0
- package/core-assets/skills/void-llm-cost-discipline/harness.yaml +15 -0
- package/core-assets/skills/void-make-pdf/.source +9 -0
- package/core-assets/skills/void-make-pdf/SKILL.md +52 -0
- package/core-assets/skills/void-make-pdf/harness.yaml +15 -0
- package/core-assets/skills/void-merge/.source +8 -0
- package/core-assets/skills/void-merge/SKILL.md +111 -0
- package/core-assets/skills/void-merge/harness.yaml +14 -0
- package/core-assets/skills/void-migrations/.source +13 -0
- package/core-assets/skills/void-migrations/SKILL.md +264 -0
- package/core-assets/skills/void-migrations/harness.yaml +17 -0
- package/core-assets/skills/void-observability/.source +12 -0
- package/core-assets/skills/void-observability/SKILL.md +245 -0
- package/core-assets/skills/void-observability/harness.yaml +15 -0
- package/core-assets/skills/void-plan/.source +15 -0
- package/core-assets/skills/void-plan/SKILL.md +239 -0
- package/core-assets/skills/void-plan/harness.yaml +14 -0
- package/core-assets/skills/void-plan-review/.source +12 -0
- package/core-assets/skills/void-plan-review/SKILL.md +128 -0
- package/core-assets/skills/void-plan-review/harness.yaml +15 -0
- package/core-assets/skills/void-qa/.source +15 -0
- package/core-assets/skills/void-qa/SKILL.md +103 -0
- package/core-assets/skills/void-qa/harness.yaml +15 -0
- package/core-assets/skills/void-refactor/.source +9 -0
- package/core-assets/skills/void-refactor/SKILL.md +220 -0
- package/core-assets/skills/void-refactor/harness.yaml +15 -0
- package/core-assets/skills/void-retrospective/.source +9 -0
- package/core-assets/skills/void-retrospective/SKILL.md +71 -0
- package/core-assets/skills/void-retrospective/harness.yaml +15 -0
- package/core-assets/skills/void-security-audit/.source +12 -0
- package/core-assets/skills/void-security-audit/SKILL.md +138 -0
- package/core-assets/skills/void-security-audit/harness.yaml +15 -0
- package/core-assets/skills/void-security-guidance/.source +12 -0
- package/core-assets/skills/void-security-guidance/SKILL.md +255 -0
- package/core-assets/skills/void-security-guidance/harness.yaml +15 -0
- package/core-assets/skills/void-source-driven-development/.source +12 -0
- package/core-assets/skills/void-source-driven-development/SKILL.md +158 -0
- package/core-assets/skills/void-source-driven-development/harness.yaml +15 -0
- package/core-assets/skills/void-tdd/.source +15 -0
- package/core-assets/skills/void-tdd/SKILL.md +394 -0
- package/core-assets/skills/void-tdd/harness.yaml +15 -0
- package/core-assets/skills/void-testing/.source +19 -0
- package/core-assets/skills/void-testing/SKILL.md +301 -0
- package/core-assets/skills/void-testing/harness.yaml +17 -0
- package/core-assets/skills/void-ticket/.source +18 -0
- package/core-assets/skills/void-ticket/SKILL.md +239 -0
- package/core-assets/skills/void-ticket/harness.yaml +14 -0
- package/core-assets/skills/void-typescript-strict/.source +10 -0
- package/core-assets/skills/void-typescript-strict/SKILL.md +294 -0
- package/core-assets/skills/void-typescript-strict/harness.yaml +17 -0
- package/core-assets/skills/void-ui-review/.source +12 -0
- package/core-assets/skills/void-ui-review/SKILL.md +97 -0
- package/core-assets/skills/void-ui-review/harness.yaml +15 -0
- package/core-assets/skills/void-verify/.source +12 -0
- package/core-assets/skills/void-verify/SKILL.md +184 -0
- package/core-assets/skills/void-verify/harness.yaml +15 -0
- package/core-assets/specialists/accessibility-specialist.source +11 -0
- package/core-assets/specialists/accessibility-specialist.yaml +30 -0
- package/core-assets/specialists/api-integration-engineer.source +11 -0
- package/core-assets/specialists/api-integration-engineer.yaml +30 -0
- package/core-assets/specialists/data-migration-engineer.source +11 -0
- package/core-assets/specialists/data-migration-engineer.yaml +30 -0
- package/core-assets/specialists/devex-docs-engineer.source +11 -0
- package/core-assets/specialists/devex-docs-engineer.yaml +29 -0
- package/core-assets/specialists/domain-architect.source +11 -0
- package/core-assets/specialists/domain-architect.yaml +28 -0
- package/core-assets/specialists/experience-designer.source +15 -0
- package/core-assets/specialists/experience-designer.yaml +38 -0
- package/core-assets/specialists/frontend-engineer.source +11 -0
- package/core-assets/specialists/frontend-engineer.yaml +32 -0
- package/core-assets/specialists/independent-code-reviewer.source +10 -0
- package/core-assets/specialists/independent-code-reviewer.yaml +37 -0
- package/core-assets/specialists/migrations.json +15 -0
- package/core-assets/specialists/observability-sre-engineer.source +11 -0
- package/core-assets/specialists/observability-sre-engineer.yaml +30 -0
- package/core-assets/specialists/pdf-specialist.source +11 -0
- package/core-assets/specialists/pdf-specialist.yaml +29 -0
- package/core-assets/specialists/performance-engineer.source +11 -0
- package/core-assets/specialists/performance-engineer.yaml +29 -0
- package/core-assets/specialists/product-challenger.source +10 -0
- package/core-assets/specialists/product-challenger.yaml +27 -0
- package/core-assets/specialists/security-engineer.source +13 -0
- package/core-assets/specialists/security-engineer.yaml +41 -0
- package/core-assets/specialists/solution-architect.source +13 -0
- package/core-assets/specialists/solution-architect.yaml +32 -0
- package/core-assets/specialists/test-qa-engineer.source +14 -0
- package/core-assets/specialists/test-qa-engineer.yaml +35 -0
- package/core-assets/specialists/visual-craft-director.source +17 -0
- package/core-assets/specialists/visual-craft-director.yaml +60 -0
- package/core-assets/templates/github/dependency-audit.mjs +54 -0
- package/core-assets/templates/github/void-dependency-audit.yml +35 -0
- package/core-assets/templates/github/void-security.yml +85 -0
- package/dist/main.js +57001 -0
- package/package.json +66 -4
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: void-cache-component-pattern
|
|
3
|
+
description: Use Next.js 16 Cache Components correctly — when to opt in ('use cache'), when to opt out ('use no cache'), cache key strategy, scoping by user/org. Default is cached; uncache deliberately.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# cache-component-pattern
|
|
7
|
+
|
|
8
|
+
Use when writing or modifying any Next.js 16 Server Component, route handler, or fetch in `app/`. Cache Components is Next 16's default-cache model — flipping the previous default-dynamic stance. Getting this right is the difference between a page rendered in 30ms (cached) and 800ms (regenerated every request).
|
|
9
|
+
|
|
10
|
+
## The default
|
|
11
|
+
|
|
12
|
+
Next 16 caches by default at the Server Component / fetch level. You opt out, not in. This is the inverse of Next 13/14.
|
|
13
|
+
|
|
14
|
+
```tsx
|
|
15
|
+
// app/blog/[slug]/page.tsx — cached by default
|
|
16
|
+
export default async function Page({ params }: { params: { slug: string } }) {
|
|
17
|
+
const post = await db.query.posts.findFirst({ where: eq(posts.slug, params.slug) });
|
|
18
|
+
return <Article post={post} />;
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This page IS cached. The `params.slug` is the key. New slugs trigger a fresh render; existing slugs serve from cache.
|
|
23
|
+
|
|
24
|
+
## When to opt OUT — `'use no cache'`
|
|
25
|
+
|
|
26
|
+
Some content cannot be cached. Tag the component or fetch:
|
|
27
|
+
|
|
28
|
+
```tsx
|
|
29
|
+
// app/dashboard/page.tsx — user-specific
|
|
30
|
+
'use no cache';
|
|
31
|
+
|
|
32
|
+
export default async function Page() {
|
|
33
|
+
const user = await getCurrentUser(); // session-dependent
|
|
34
|
+
return <Dashboard user={user} />;
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Use `'use no cache'` when the response depends on:
|
|
39
|
+
|
|
40
|
+
- Current user identity (`getCurrentUser`, `cookies()`, `headers()`)
|
|
41
|
+
- Live data with sub-minute staleness requirements (real-time dashboards, inbox)
|
|
42
|
+
- Random sampling, A/B tests at render time
|
|
43
|
+
- `Date.now()` or similar non-deterministic inputs you don't want to key
|
|
44
|
+
|
|
45
|
+
The rule: if the same URL could legitimately render two different responses to two different users, opt out.
|
|
46
|
+
|
|
47
|
+
## Per-fetch opt-out
|
|
48
|
+
|
|
49
|
+
Inside a component that's MOSTLY cacheable but has one dynamic call:
|
|
50
|
+
|
|
51
|
+
```tsx
|
|
52
|
+
export default async function Page() {
|
|
53
|
+
// Cached
|
|
54
|
+
const post = await db.query.posts.findFirst({ where: eq(posts.slug, slug) });
|
|
55
|
+
|
|
56
|
+
// Not cached — re-fetched every request
|
|
57
|
+
const liveViewerCount = await fetch('https://api/views', { cache: 'no-store' });
|
|
58
|
+
|
|
59
|
+
return <Article post={post} viewers={liveViewerCount} />;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`{ cache: 'no-store' }` opts out one fetch. The page stays cached for the parts that can be.
|
|
64
|
+
|
|
65
|
+
## Cache key strategy
|
|
66
|
+
|
|
67
|
+
The key for a cached component is **automatically derived** from:
|
|
68
|
+
|
|
69
|
+
- Route params (`[slug]`, `[id]`)
|
|
70
|
+
- Search params used in the component (only those READ; uses are tracked)
|
|
71
|
+
- Cookies / headers used (tracked similarly — but reading these usually opts you out automatically)
|
|
72
|
+
|
|
73
|
+
You influence the key by what you read. To cache one variant per locale:
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
// app/[locale]/page.tsx
|
|
77
|
+
export default async function Page({ params }: { params: { locale: string } }) {
|
|
78
|
+
return <Home locale={params.locale} />;
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`/en` and `/fr` are separate cache entries because `locale` is in the URL.
|
|
83
|
+
|
|
84
|
+
To cache one variant per org WITHOUT putting org in the URL: don't. Put it in the URL. Caching cross-cuts user scopes is a security accident waiting to happen.
|
|
85
|
+
|
|
86
|
+
## The "shared cache across users" trap
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
// ✗ DANGEROUS — caches user-specific data under a route-only key
|
|
90
|
+
export default async function Page() {
|
|
91
|
+
const user = await getCurrentUser();
|
|
92
|
+
const posts = await db.query.posts.findMany({ where: eq(posts.authorId, user.id) });
|
|
93
|
+
return <PostList posts={posts} />;
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
If Next caches this without realizing `user.id` is in play, user A could see user B's posts. The `getCurrentUser` call reads cookies, which marks the page dynamic — but only if Next is configured correctly. **Always add `'use no cache'` when the response depends on the user, even if Next "would have" detected it.** Belt and braces; the cost of being explicit is zero, the cost of a leak is unbounded.
|
|
98
|
+
|
|
99
|
+
## `revalidate` for time-based freshness
|
|
100
|
+
|
|
101
|
+
Cached but with a maximum age:
|
|
102
|
+
|
|
103
|
+
```tsx
|
|
104
|
+
export const revalidate = 60; // seconds
|
|
105
|
+
|
|
106
|
+
export default async function Page() { ... }
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Use cases: news headlines, leaderboards, dashboards updated every minute. The cache serves stale content for up to 60s, then re-renders on the next request.
|
|
110
|
+
|
|
111
|
+
For tag-based revalidation (invalidate specific entries after a mutation):
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
import { revalidateTag } from 'next/cache';
|
|
115
|
+
|
|
116
|
+
export async function publishPost() {
|
|
117
|
+
await db.insert(posts).values(...);
|
|
118
|
+
revalidateTag('posts-list');
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
In the cached component: `fetch(url, { next: { tags: ['posts-list'] } })`. The tag links them.
|
|
123
|
+
|
|
124
|
+
## `revalidatePath` vs `revalidateTag`
|
|
125
|
+
|
|
126
|
+
| Function | Use when | Scope |
|
|
127
|
+
|---|---|---|
|
|
128
|
+
| `revalidatePath('/blog/[slug]', 'page')` | One specific page invalidated after a mutation | Single path |
|
|
129
|
+
| `revalidatePath('/blog', 'layout')` | A whole subtree invalidated (rare) | Layout + all children |
|
|
130
|
+
| `revalidateTag('posts-list')` | Multiple pages share the same data | Every fetch with that tag |
|
|
131
|
+
|
|
132
|
+
Default to `revalidateTag`. Path-based revalidation is fragile (parameterized paths are tricky).
|
|
133
|
+
|
|
134
|
+
## Anti-patterns
|
|
135
|
+
|
|
136
|
+
- ✗ **`'use no cache'` at the layout level** — blanket-opts-out the entire subtree. Use it on specific routes.
|
|
137
|
+
- ✗ **Caching `getCurrentUser()` results** — `cache(getCurrentUser)` is a request-scoped React `cache`, NOT a cross-request Next cache; don't confuse the two
|
|
138
|
+
- ✗ **`revalidate = 0`** — equivalent to `'use no cache'` but less explicit. Use the directive.
|
|
139
|
+
- ✗ **Broad `revalidatePath('/')` after every mutation** — kills cache hit rate; be surgical
|
|
140
|
+
- ✗ **Mixing `cache: 'no-store'` AND `next: { revalidate }` on the same fetch** — they conflict; pick one
|
|
141
|
+
|
|
142
|
+
## Debug: "is this actually cached?"
|
|
143
|
+
|
|
144
|
+
Add a server-side `console.log(Date.now())` in the Server Component. Hit refresh. If the timestamp changes, the page is NOT cached. If it's stable across refreshes, it is.
|
|
145
|
+
|
|
146
|
+
Or use Next's built-in `?_cache-debug=1` (if available in your version) to inspect cache hit/miss per route in DevTools.
|
|
147
|
+
|
|
148
|
+
## Workflow
|
|
149
|
+
|
|
150
|
+
1. **Before writing the component**, ask: does the response depend on the user, on cookies, or on "right now"?
|
|
151
|
+
2. **If yes**, top of the file: `'use no cache';`. Done.
|
|
152
|
+
3. **If no**, leave it cached. No directive needed.
|
|
153
|
+
4. **Per-fetch overrides** for surgical exceptions (one dynamic fetch in a mostly-cached page).
|
|
154
|
+
5. **Tag your fetches** if they'll need invalidation after mutations (`next: { tags: [...] }`).
|
|
155
|
+
6. **Invalidate from Server Actions** via `revalidateTag` after the mutation succeeds.
|
|
156
|
+
|
|
157
|
+
## Composition
|
|
158
|
+
|
|
159
|
+
- `void-client-vs-server-component` — only Server Components participate in Cache Components.
|
|
160
|
+
- `void-server-action` — actions call `revalidateTag` / `revalidatePath` after mutations.
|
|
161
|
+
- `void-security-guidance` — explicit `'use no cache'` on user-scoped content is a security control, not just a perf one.
|
|
162
|
+
- `void-observability` — log cache misses on routes you expect cached; surface as a perf signal.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Harness metadata for this skill. Not part of the Agent Skills
|
|
2
|
+
# specification, and never copied into a consumer project: the SKILL.md
|
|
3
|
+
# beside it stays portable and validates against the official six fields.
|
|
4
|
+
# Read by this repository's graph, and by the installer for `runtimes`.
|
|
5
|
+
kind: standard
|
|
6
|
+
owner: folpe
|
|
7
|
+
runtimes: [claude, codex]
|
|
8
|
+
enforcement:
|
|
9
|
+
floor: ci
|
|
10
|
+
inline:
|
|
11
|
+
claude: active
|
|
12
|
+
codex: active
|
|
13
|
+
hermes: ci-only
|
|
14
|
+
eval_targets: [claude/anthropic/opus]
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Sources for `void-instrumentation-setup` skill (see `docs/plans/skill-audits/void-instrumentation-setup.md` for the full audit):
|
|
2
|
+
|
|
3
|
+
- No external source listed in the audit. Authored against the Next.js 16 instrumentation.ts mechanism (register() export, NEXT_RUNTIME edge/node split) and the Sentry + OpenTelemetry + pino composition.
|
|
4
|
+
|
|
5
|
+
Strategy: distill. Wiring pattern authored natively for Next 16; no upstream skill was adapted.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: void-instrumentation-setup
|
|
3
|
+
description: Wire instrumentation.ts in Next.js 16 — Sentry, pino logger, OpenTelemetry traces. Edge vs Node runtime split. The single place observability is bootstrapped.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# instrumentation-setup
|
|
7
|
+
|
|
8
|
+
Use when setting up observability in a fresh Next.js app, OR when adding a new instrumentation tool (Sentry → +OTel, +Datadog, etc.). The pattern: ONE `instrumentation.ts` at the project root, conditionally initializes per runtime.
|
|
9
|
+
|
|
10
|
+
Composes with `void-observability` (the doctrine: pino, structured logs, Sentry user scope). This skill is the Next-specific wiring.
|
|
11
|
+
|
|
12
|
+
## File location
|
|
13
|
+
|
|
14
|
+
```
|
|
15
|
+
apps/<app>/
|
|
16
|
+
├── instrumentation.ts # the bootstrap
|
|
17
|
+
├── instrumentation-node.ts # Node-runtime-specific
|
|
18
|
+
├── instrumentation-edge.ts # Edge-runtime-specific (optional)
|
|
19
|
+
└── src/
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Next 16 auto-loads `instrumentation.ts` at server startup (both runtimes). `instrumentation-{node,edge}.ts` are convention-driven splits.
|
|
23
|
+
|
|
24
|
+
## Canonical bootstrap
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
// apps/web/instrumentation.ts
|
|
28
|
+
export async function register() {
|
|
29
|
+
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
|
30
|
+
await import('./instrumentation-node');
|
|
31
|
+
}
|
|
32
|
+
if (process.env.NEXT_RUNTIME === 'edge') {
|
|
33
|
+
await import('./instrumentation-edge');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
The dynamic imports keep edge bundles slim (Sentry's Node SDK doesn't ship to Edge runtime).
|
|
39
|
+
|
|
40
|
+
## Node runtime setup
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// apps/web/instrumentation-node.ts
|
|
44
|
+
import * as Sentry from '@sentry/nextjs';
|
|
45
|
+
import { trace } from '@opentelemetry/api';
|
|
46
|
+
import { NodeSDK } from '@opentelemetry/sdk-node';
|
|
47
|
+
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
|
|
48
|
+
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
49
|
+
import { logger, env } from '@repo/core';
|
|
50
|
+
|
|
51
|
+
// 1. Sentry
|
|
52
|
+
Sentry.init({
|
|
53
|
+
dsn: env.SENTRY_DSN,
|
|
54
|
+
environment: env.NODE_ENV,
|
|
55
|
+
tracesSampleRate: env.NODE_ENV === 'production' ? 0.1 : 1.0,
|
|
56
|
+
beforeSend: (event) => {
|
|
57
|
+
if (event.user?.id) event.user.id = hash(event.user.id); // anonymize
|
|
58
|
+
return event;
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// 2. OpenTelemetry (if you ship traces outside Sentry)
|
|
63
|
+
if (env.OTEL_EXPORTER_OTLP_ENDPOINT) {
|
|
64
|
+
const sdk = new NodeSDK({
|
|
65
|
+
traceExporter: new OTLPTraceExporter({
|
|
66
|
+
url: env.OTEL_EXPORTER_OTLP_ENDPOINT,
|
|
67
|
+
}),
|
|
68
|
+
instrumentations: [getNodeAutoInstrumentations()],
|
|
69
|
+
});
|
|
70
|
+
sdk.start();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 3. pino is initialized eagerly in @repo/core/logger — nothing to do here.
|
|
74
|
+
|
|
75
|
+
logger.info({ event: 'app.boot', runtime: 'nodejs' });
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Edge runtime setup
|
|
79
|
+
|
|
80
|
+
```ts
|
|
81
|
+
// apps/web/instrumentation-edge.ts
|
|
82
|
+
import * as Sentry from '@sentry/nextjs';
|
|
83
|
+
import { env } from '@repo/core';
|
|
84
|
+
|
|
85
|
+
Sentry.init({
|
|
86
|
+
dsn: env.SENTRY_DSN,
|
|
87
|
+
environment: env.NODE_ENV,
|
|
88
|
+
tracesSampleRate: 0.1,
|
|
89
|
+
transportOptions: { fetchOptions: { keepalive: true } }, // Edge fetch quirk
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// No OTel, no pino — Edge runtime doesn't support them.
|
|
93
|
+
// Use Sentry.captureMessage / addBreadcrumb for structured-ish logs.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Edge can't `fs.write`, can't load native modules, can't use Node `Buffer` outside polyfills. Keep instrumentation minimal.
|
|
97
|
+
|
|
98
|
+
## What lives where (decision table)
|
|
99
|
+
|
|
100
|
+
| Concern | Where |
|
|
101
|
+
|---|---|
|
|
102
|
+
| Sentry init | `instrumentation-node.ts` AND `instrumentation-edge.ts` |
|
|
103
|
+
| OpenTelemetry | `instrumentation-node.ts` only |
|
|
104
|
+
| pino logger | `@repo/core/logger` (auto-initialized on import) |
|
|
105
|
+
| Sentry user scope per-request | Server Action / Route handler wrapper (`withTraceContext`) |
|
|
106
|
+
| Request-level traces | OTel auto-instrumentation (Node) |
|
|
107
|
+
| Client-side errors | `app/global-error.tsx` + `Sentry.init` in a `'use client'` provider |
|
|
108
|
+
|
|
109
|
+
## Client-side Sentry
|
|
110
|
+
|
|
111
|
+
Server `instrumentation.ts` does NOT cover the browser. For client errors, add a separate provider:
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
// apps/web/src/app/sentry-provider.tsx
|
|
115
|
+
'use client';
|
|
116
|
+
|
|
117
|
+
import * as Sentry from '@sentry/nextjs';
|
|
118
|
+
import { useEffect } from 'react';
|
|
119
|
+
|
|
120
|
+
let initialized = false;
|
|
121
|
+
|
|
122
|
+
export function SentryProvider({ children }: { children: React.ReactNode }) {
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (initialized) return;
|
|
125
|
+
initialized = true;
|
|
126
|
+
Sentry.init({
|
|
127
|
+
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
|
128
|
+
tracesSampleRate: 0.1,
|
|
129
|
+
replaysSessionSampleRate: 0.1,
|
|
130
|
+
});
|
|
131
|
+
}, []);
|
|
132
|
+
return <>{children}</>;
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Wire in `app/layout.tsx`. The `NEXT_PUBLIC_*` env is the only secret-ish value that's safe in the bundle.
|
|
137
|
+
|
|
138
|
+
## Anti-patterns
|
|
139
|
+
|
|
140
|
+
- ✗ **Calling `Sentry.init` inside route handlers** — re-inits every request, leaks event listeners
|
|
141
|
+
- ✗ **Same Sentry sample rate in dev and prod** — at 1.0 prod you'll exhaust your event quota
|
|
142
|
+
- ✗ **Logging PII raw** — `beforeSend` should redact user.id, email, etc. Hash or strip.
|
|
143
|
+
- ✗ **OTel auto-instrumentation in Edge** — silently fails because Edge can't load Node native bindings
|
|
144
|
+
- ✗ **One instrumentation.ts that branches on runtime inline** — the dynamic-import pattern keeps bundles clean
|
|
145
|
+
|
|
146
|
+
## Verification checklist
|
|
147
|
+
|
|
148
|
+
After setup, in dev:
|
|
149
|
+
|
|
150
|
+
1. Throw in a Server Component — should surface in Sentry
|
|
151
|
+
2. Throw in a Server Action — should surface
|
|
152
|
+
3. Throw in a Client Component — should surface (client-side init)
|
|
153
|
+
4. Check that user ID is hashed in Sentry events
|
|
154
|
+
5. Check that NODE_ENV-conditional behavior (sample rate) actually differs
|
|
155
|
+
|
|
156
|
+
## Composition
|
|
157
|
+
|
|
158
|
+
- `void-observability` — pino + Sentry doctrine (this skill is the Next wiring).
|
|
159
|
+
- `void-security-guidance` — Sentry user scope MUST be hashed; no PII in event payloads.
|
|
160
|
+
- `void-env-validation` — `SENTRY_DSN`, `OTEL_EXPORTER_OTLP_ENDPOINT` validated via Zod in `@repo/core/env`.
|
|
161
|
+
- `void-loading-error-boundaries` — `error.tsx` uses Sentry.captureException; this skill ensures Sentry is initialized when that fires.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Harness metadata for this skill. Not part of the Agent Skills
|
|
2
|
+
# specification, and never copied into a consumer project: the SKILL.md
|
|
3
|
+
# beside it stays portable and validates against the official six fields.
|
|
4
|
+
# Read by this repository's graph, and by the installer for `runtimes`.
|
|
5
|
+
kind: standard
|
|
6
|
+
owner: folpe
|
|
7
|
+
runtimes: [claude, codex]
|
|
8
|
+
enforcement:
|
|
9
|
+
floor: ci
|
|
10
|
+
inline:
|
|
11
|
+
claude: active
|
|
12
|
+
codex: active
|
|
13
|
+
hermes: ci-only
|
|
14
|
+
eval_targets: [claude/anthropic/opus]
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Sources for `void-loading-error-boundaries` skill (see `docs/plans/skill-audits/void-loading-error-boundaries.md` for the full audit):
|
|
2
|
+
|
|
3
|
+
- No external source listed in the audit. Authored against the App Router boundary file conventions (loading.tsx, error.tsx, not-found.tsx) and the Sentry capture point in error boundaries.
|
|
4
|
+
|
|
5
|
+
Strategy: distill. Placement rules authored natively for the App Router; no upstream skill was adapted.
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: void-loading-error-boundaries
|
|
3
|
+
description: Place loading.tsx, error.tsx, not-found.tsx files at the right level — neither too high (poor UX) nor too low (unhandled errors). The skeleton matches the layout it replaces.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# loading-error-boundaries
|
|
7
|
+
|
|
8
|
+
Use when adding or restructuring routes in `app/`. Next.js boundary files (`loading.tsx`, `error.tsx`, `not-found.tsx`) define UX during Server Component rendering, server failures, and missing-resource cases. Placement matters: too high = whole page flashes on partial load; too low = errors leak past the intended catch.
|
|
9
|
+
|
|
10
|
+
## The 4 boundary files
|
|
11
|
+
|
|
12
|
+
| File | Wraps | Renders when |
|
|
13
|
+
|---|---|---|
|
|
14
|
+
| `loading.tsx` | Sibling `page.tsx` in a Suspense boundary | Async server work in progress |
|
|
15
|
+
| `error.tsx` | Sibling `page.tsx` + descendants in an Error Boundary | Server throws OR client error |
|
|
16
|
+
| `not-found.tsx` | Triggered by `notFound()` call | `notFound()` from a Server Component / handler |
|
|
17
|
+
| `global-error.tsx` | Wraps the root `app/layout.tsx` itself | Layout throws (rare) |
|
|
18
|
+
|
|
19
|
+
`error.tsx` and `global-error.tsx` MUST be Client Components (they catch React errors).
|
|
20
|
+
|
|
21
|
+
## Placement: as low as practical
|
|
22
|
+
|
|
23
|
+
Place boundary files **at the segment that owns the unique loading state**, not at the root.
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
app/
|
|
27
|
+
├── (app)/
|
|
28
|
+
│ ├── layout.tsx # auth check, app shell
|
|
29
|
+
│ ├── dashboard/
|
|
30
|
+
│ │ ├── page.tsx
|
|
31
|
+
│ │ ├── loading.tsx # skeleton of dashboard widgets
|
|
32
|
+
│ │ └── error.tsx # error display in app shell context
|
|
33
|
+
│ └── settings/
|
|
34
|
+
│ ├── page.tsx
|
|
35
|
+
│ └── loading.tsx # skeleton of settings form
|
|
36
|
+
└── (marketing)/
|
|
37
|
+
├── layout.tsx
|
|
38
|
+
└── blog/
|
|
39
|
+
├── [slug]/
|
|
40
|
+
│ ├── page.tsx
|
|
41
|
+
│ ├── loading.tsx # article skeleton
|
|
42
|
+
│ └── error.tsx # "couldn't load article" message
|
|
43
|
+
└── page.tsx # blog index (no skeleton — small enough)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Per-route boundaries mean a slow `/dashboard` doesn't blank out `/settings` when the user is on `/settings`. Each route streams in independently.
|
|
47
|
+
|
|
48
|
+
## Skeleton design: match the final layout
|
|
49
|
+
|
|
50
|
+
A skeleton's job is to **reserve the space** the real content will occupy. The user's eye should not jump when the content arrives.
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
// app/(app)/dashboard/loading.tsx
|
|
54
|
+
export default function Loading() {
|
|
55
|
+
return (
|
|
56
|
+
<div className="grid grid-cols-3 gap-4 p-6">
|
|
57
|
+
<Skeleton className="h-32" /> {/* matches card height */}
|
|
58
|
+
<Skeleton className="h-32" />
|
|
59
|
+
<Skeleton className="h-32" />
|
|
60
|
+
<Skeleton className="col-span-3 h-64" />{/* matches chart */}
|
|
61
|
+
</div>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- ✗ Spinner in the middle of the page — gives no spatial cue, content appearance is jarring
|
|
67
|
+
- ✗ Skeleton that's the wrong height — layout shift on content arrival = bad CLS
|
|
68
|
+
- ✓ Skeleton bricks matching the final layout shape, neutral gray fill, optional shimmer
|
|
69
|
+
|
|
70
|
+
For text content: 3-5 lines of skeleton bars at varying widths look more realistic than perfect rectangles.
|
|
71
|
+
|
|
72
|
+
## `error.tsx`: handle, don't display the stack
|
|
73
|
+
|
|
74
|
+
Error boundary should:
|
|
75
|
+
|
|
76
|
+
- Hide the technical detail from end users
|
|
77
|
+
- Log to Sentry (composes with `void-observability`)
|
|
78
|
+
- Offer a recovery path (reset button, link back)
|
|
79
|
+
|
|
80
|
+
```tsx
|
|
81
|
+
'use client';
|
|
82
|
+
|
|
83
|
+
import { useEffect } from 'react';
|
|
84
|
+
import * as Sentry from '@sentry/nextjs';
|
|
85
|
+
|
|
86
|
+
export default function Error({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
|
87
|
+
useEffect(() => {
|
|
88
|
+
Sentry.captureException(error);
|
|
89
|
+
}, [error]);
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<div className="p-6">
|
|
93
|
+
<h2>Quelque chose s'est mal passé.</h2>
|
|
94
|
+
<p>L'équipe est notifiée. Vous pouvez réessayer.</p>
|
|
95
|
+
<button onClick={() => reset()}>Réessayer</button>
|
|
96
|
+
</div>
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `digest` prop is the server-side error ID — useful to correlate Sentry with the user report (display it discreetly: `<small>ID: {error.digest}</small>`).
|
|
102
|
+
|
|
103
|
+
## `not-found.tsx` — the route's own 404
|
|
104
|
+
|
|
105
|
+
```tsx
|
|
106
|
+
// app/(marketing)/blog/[slug]/not-found.tsx
|
|
107
|
+
export default function NotFound() {
|
|
108
|
+
return (
|
|
109
|
+
<div className="p-6 text-center">
|
|
110
|
+
<h1>Article introuvable</h1>
|
|
111
|
+
<p>Cet article n'existe pas ou a été supprimé.</p>
|
|
112
|
+
<Link href="/blog">← Retour aux articles</Link>
|
|
113
|
+
</div>
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Triggered from a Server Component:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
import { notFound } from 'next/navigation';
|
|
122
|
+
|
|
123
|
+
export default async function Article({ params }) {
|
|
124
|
+
const post = await db.query.posts.findFirst({ where: eq(posts.slug, params.slug) });
|
|
125
|
+
if (!post) notFound();
|
|
126
|
+
return <ArticleView post={post} />;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Without a `not-found.tsx` at the route, Next falls back to the nearest ancestor. Provide one at the level that knows the resource — a blog `not-found` is contextually richer than the root 404.
|
|
131
|
+
|
|
132
|
+
## `global-error.tsx` — last resort
|
|
133
|
+
|
|
134
|
+
Catches errors in `app/layout.tsx` itself (and its providers). Rare; usually empty until needed.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
// app/global-error.tsx
|
|
138
|
+
'use client';
|
|
139
|
+
|
|
140
|
+
export default function GlobalError({ error, reset }) {
|
|
141
|
+
return (
|
|
142
|
+
<html>
|
|
143
|
+
<body>
|
|
144
|
+
<h1>Application non disponible.</h1>
|
|
145
|
+
<button onClick={() => reset()}>Recharger</button>
|
|
146
|
+
</body>
|
|
147
|
+
</html>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Must render its own `<html>` and `<body>` because it replaces the root layout entirely.
|
|
153
|
+
|
|
154
|
+
## Anti-patterns
|
|
155
|
+
|
|
156
|
+
- ✗ **One `loading.tsx` at app root** — every route shows the same skeleton; no granularity
|
|
157
|
+
- ✗ **Spinner-only loading** — no spatial cue, content appearance is jarring
|
|
158
|
+
- ✗ **Error boundary displaying the raw stack** — leaks internal info, scares users
|
|
159
|
+
- ✗ **`not-found.tsx` only at the root** — every "not found" looks the same; misses context
|
|
160
|
+
- ✗ **No `useEffect` Sentry capture in error.tsx** — errors swallowed silently
|
|
161
|
+
- ✗ **Catching errors in components via try/catch instead of `error.tsx`** — bypasses React's error boundary mechanism
|
|
162
|
+
|
|
163
|
+
## Workflow
|
|
164
|
+
|
|
165
|
+
1. **At each route segment**, ask: does this content need its own loading state?
|
|
166
|
+
2. **If yes**, write a `loading.tsx` whose skeleton matches the final layout's shape.
|
|
167
|
+
3. **For routes that fetch data**, write an `error.tsx` with Sentry capture + recovery action.
|
|
168
|
+
4. **For routes with parameterized resources** ([slug], [id]), write a `not-found.tsx` and call `notFound()` when the resource is absent.
|
|
169
|
+
5. **`global-error.tsx` only when** root layout starts having logic that can fail.
|
|
170
|
+
|
|
171
|
+
## Composition
|
|
172
|
+
|
|
173
|
+
- `void-cache-component-pattern` — cached pages have predictable load times; loading.tsx is mostly for the cold cache case.
|
|
174
|
+
- `void-parallel-routes-slots` — each `@slot` can have its own `loading.tsx` / `error.tsx`.
|
|
175
|
+
- `void-observability` — `error.tsx` is the Sentry capture point for server errors.
|
|
176
|
+
- `void-accessibility-check` — skeletons should have `aria-busy` or `aria-live="polite"` regions.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Harness metadata for this skill. Not part of the Agent Skills
|
|
2
|
+
# specification, and never copied into a consumer project: the SKILL.md
|
|
3
|
+
# beside it stays portable and validates against the official six fields.
|
|
4
|
+
# Read by this repository's graph, and by the installer for `runtimes`.
|
|
5
|
+
kind: standard
|
|
6
|
+
owner: folpe
|
|
7
|
+
runtimes: [claude, codex]
|
|
8
|
+
enforcement:
|
|
9
|
+
floor: ci
|
|
10
|
+
inline:
|
|
11
|
+
claude: active
|
|
12
|
+
codex: active
|
|
13
|
+
hermes: ci-only
|
|
14
|
+
eval_targets: [claude/anthropic/opus]
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
Sources for `void-parallel-routes-slots` skill (see `docs/plans/skill-audits/void-parallel-routes-slots.md` for the full audit):
|
|
2
|
+
|
|
3
|
+
- No external source listed in the audit. Authored against the Next.js routing primitives (parallel routes @slot, intercepting routes (.)foo) and their over-application traps.
|
|
4
|
+
|
|
5
|
+
Strategy: distill. WIN-vs-LOSE guidance authored natively for the Next routing primitives; no upstream skill was adapted.
|