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,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: void-client-vs-server-component
|
|
3
|
+
description: Decide which components run on server vs client in a React 19 / Next.js app and place the 'use client' boundary correctly. Server Components by default; client is the exception.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# client-vs-server-component
|
|
7
|
+
|
|
8
|
+
Use when creating any React 19 component in a project that supports Server Components (Next.js App Router, similar). The choice is **not** "type 'use client' if the component is interactive" — that's the lazy heuristic that leads to 80% of the app shipping to the browser.
|
|
9
|
+
|
|
10
|
+
## The default
|
|
11
|
+
|
|
12
|
+
**Server Component (no `'use client'`) by default.** Add `'use client'` only when you need browser-only APIs:
|
|
13
|
+
|
|
14
|
+
- React state hooks: `useState`, `useReducer`, `useContext`, `useRef`
|
|
15
|
+
- Effects: `useEffect`, `useLayoutEffect`
|
|
16
|
+
- Browser APIs: `window`, `document`, `localStorage`, `IntersectionObserver`
|
|
17
|
+
- Event handlers attached at component level (`onClick`, `onChange`, `onSubmit`)
|
|
18
|
+
- Third-party libraries that use any of the above
|
|
19
|
+
|
|
20
|
+
If your component does **none** of these — even if it ends up inside a Client Component — leave it server. RSC composition lets you pass server-rendered children into client wrappers.
|
|
21
|
+
|
|
22
|
+
## The boundary placement rule
|
|
23
|
+
|
|
24
|
+
Push `'use client'` **as far down the tree as possible**. Bad pattern:
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// ✗ app/dashboard/page.tsx
|
|
28
|
+
'use client'; // entire page becomes client
|
|
29
|
+
import { useState } from 'react';
|
|
30
|
+
import { UserList } from './UserList'; // also becomes client, even if it didn't need to
|
|
31
|
+
|
|
32
|
+
export default function Page() {
|
|
33
|
+
const [filter, setFilter] = useState('');
|
|
34
|
+
return (
|
|
35
|
+
<>
|
|
36
|
+
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
|
|
37
|
+
<UserList filter={filter} />
|
|
38
|
+
</>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Good pattern — isolate the interactive part:
|
|
44
|
+
|
|
45
|
+
```tsx
|
|
46
|
+
// ✓ app/dashboard/page.tsx (Server)
|
|
47
|
+
import { UserList } from './UserList'; // stays server
|
|
48
|
+
import { FilterInput } from './FilterInput'; // client island
|
|
49
|
+
|
|
50
|
+
export default function Page() {
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<FilterInput />
|
|
54
|
+
<UserList />
|
|
55
|
+
</>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
`FilterInput` owns the `useState`. It can either:
|
|
61
|
+
- Push the filter to the URL and let `UserList` re-render server-side via search params
|
|
62
|
+
- Or render `<UserList />` as a child it receives from the server and re-filter client-side from a snapshot
|
|
63
|
+
|
|
64
|
+
The pattern: **client wraps server children**. Server Component as child of Client Component is allowed; the inverse is not.
|
|
65
|
+
|
|
66
|
+
## State + data: who owns what
|
|
67
|
+
|
|
68
|
+
| Concern | Lives on | Component type |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| URL params (`?filter=...`) | Server (read via `searchParams`) | Server Component |
|
|
71
|
+
| Persistent server-side data | Server (DB / cache) | Server Component fetching directly |
|
|
72
|
+
| Transient UI state (open/closed, focus, input value) | Client (`useState`) | Client Component |
|
|
73
|
+
| Cross-component client state | Client (URL > Zustand > Context) | Client provider tree |
|
|
74
|
+
| Optimistic updates | Client (capture-queue, see `void-offline-first-mutation`) | Client component |
|
|
75
|
+
|
|
76
|
+
When in doubt, ask: "does this state need to survive a hard refresh?" If yes → server (or URL). If no → client.
|
|
77
|
+
|
|
78
|
+
## Anti-patterns
|
|
79
|
+
|
|
80
|
+
- ✗ **`'use client'` at the top of `app/layout.tsx`** — destroys server rendering for the whole subtree
|
|
81
|
+
- ✗ **Wrapping all components in client just to use `<Link>`** — Next's `Link` works in Server Components
|
|
82
|
+
- ✗ **`useEffect` to fetch data in a Client Component when a Server Component could fetch it on the server** — slow, waterfall, ugly loading states
|
|
83
|
+
- ✗ **`import { db } from '@repo/db'` in a Client Component** — caught by `no-db-in-components` hook; refactor as a Server Action or Server Component fetch
|
|
84
|
+
- ✗ **Marking a leaf component `'use client'` because its parent is** — children inherit client mode if the parent passes them through children prop into a client wrapper; explicit `'use client'` is only needed where state/effects live
|
|
85
|
+
|
|
86
|
+
## Forms — special case
|
|
87
|
+
|
|
88
|
+
Use **Server Actions** (`'use server'`) called from a Server Component form. The form HTML is server-rendered. JS hydration adds progressive enhancement:
|
|
89
|
+
|
|
90
|
+
```tsx
|
|
91
|
+
// app/contact/page.tsx (Server)
|
|
92
|
+
import { sendContact } from './actions';
|
|
93
|
+
|
|
94
|
+
export default function Page() {
|
|
95
|
+
return (
|
|
96
|
+
<form action={sendContact}>
|
|
97
|
+
<input name="email" type="email" required />
|
|
98
|
+
<button type="submit">Send</button>
|
|
99
|
+
</form>
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Need controlled state for validation feedback? Wrap just the input in a client component. The form action stays server.
|
|
105
|
+
|
|
106
|
+
## Performance signal: "JS payload"
|
|
107
|
+
|
|
108
|
+
Open DevTools → Network tab → filter by JS. If your `app/dashboard` route ships > 100KB of JS, you have too much client. Audit which components are `'use client'`. 90% should not be.
|
|
109
|
+
|
|
110
|
+
## Composition
|
|
111
|
+
|
|
112
|
+
- `void-cache-component-pattern` — Server Components ARE the cache substrate; mark `'use cache'` for caching.
|
|
113
|
+
- `void-state-architecture` — guides client-state placement once you've decided to use Client Components.
|
|
114
|
+
- `void-form-pattern` — react-hook-form + Zod for client-controlled forms (Server Actions otherwise).
|
|
115
|
+
- the `pack-react` doctrine module on components — components are pure UI either way (no DB, no fetch via service).
|
|
116
|
+
- `void-hexagonal-architecture` — Server Components can call services directly (they're the boundary); Client Components call Server Actions.
|
|
@@ -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,7 @@
|
|
|
1
|
+
Sources for `void-form-pattern` skill (see `docs/plans/skill-audits/void-form-pattern.md` for the full audit):
|
|
2
|
+
|
|
3
|
+
- react-hook-form docs — canonical controlled-form pattern and skeleton.
|
|
4
|
+
- @hookform/resolvers Zod adapter — schema-sharing resolver pattern.
|
|
5
|
+
- Distilled by repeated form bugs on real projects — the "when NOT to use" and shared-schema discipline.
|
|
6
|
+
|
|
7
|
+
Strategy: distill. Default stack (react-hook-form + Zod resolver + shared schema with Server Action) eliminates the recurring "where does validation live" question.
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: void-form-pattern
|
|
3
|
+
description: "Build forms with react-hook-form + Zod resolver (void-machine default): validation, error UX, submit flow with Server Actions. Composes with server-action."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# form-pattern
|
|
7
|
+
|
|
8
|
+
Use when building any form with > 1 field, any field with validation, or any submit that triggers a mutation. The default in void-machine is **react-hook-form** + **Zod resolver** for client-side controlled forms, paired with **Server Actions** on submit.
|
|
9
|
+
|
|
10
|
+
For a single-field action (search input, toggle, "delete" button), use a Server Action directly — no library needed.
|
|
11
|
+
|
|
12
|
+
## The default stack
|
|
13
|
+
|
|
14
|
+
- `react-hook-form` — form state, registration, validation orchestration
|
|
15
|
+
- `@hookform/resolvers/zod` — bridge to Zod
|
|
16
|
+
- `zod` — schema (single source of truth: same schema validates client AND server)
|
|
17
|
+
- Server Action (`void-server-action`) — the submit target
|
|
18
|
+
|
|
19
|
+
## Canonical skeleton
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
'use client';
|
|
23
|
+
|
|
24
|
+
import { useForm } from 'react-hook-form';
|
|
25
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
import { saveContact } from '@/actions/contact';
|
|
28
|
+
|
|
29
|
+
const FormSchema = z.object({
|
|
30
|
+
email: z.string().email('Format email invalide'),
|
|
31
|
+
message: z.string().min(10, 'Min 10 caractères').max(2000),
|
|
32
|
+
});
|
|
33
|
+
type FormValues = z.infer<typeof FormSchema>;
|
|
34
|
+
|
|
35
|
+
export function ContactForm() {
|
|
36
|
+
const form = useForm<FormValues>({
|
|
37
|
+
resolver: zodResolver(FormSchema),
|
|
38
|
+
defaultValues: { email: '', message: '' },
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
async function onSubmit(values: FormValues) {
|
|
42
|
+
const result = await saveContact(values);
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
form.setError('root', { message: result.error });
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
form.reset();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<form onSubmit={form.handleSubmit(onSubmit)} noValidate>
|
|
52
|
+
<label>
|
|
53
|
+
Email
|
|
54
|
+
<input {...form.register('email')} type="email" aria-invalid={!!form.formState.errors.email} />
|
|
55
|
+
{form.formState.errors.email && <span role="alert">{form.formState.errors.email.message}</span>}
|
|
56
|
+
</label>
|
|
57
|
+
|
|
58
|
+
<label>
|
|
59
|
+
Message
|
|
60
|
+
<textarea {...form.register('message')} aria-invalid={!!form.formState.errors.message} />
|
|
61
|
+
{form.formState.errors.message && <span role="alert">{form.formState.errors.message.message}</span>}
|
|
62
|
+
</label>
|
|
63
|
+
|
|
64
|
+
{form.formState.errors.root && <p role="alert">{form.formState.errors.root.message}</p>}
|
|
65
|
+
|
|
66
|
+
<button type="submit" disabled={form.formState.isSubmitting}>
|
|
67
|
+
{form.formState.isSubmitting ? 'Envoi…' : 'Envoyer'}
|
|
68
|
+
</button>
|
|
69
|
+
</form>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Five things to notice:
|
|
75
|
+
|
|
76
|
+
1. **One Zod schema** — same schema is imported by the Server Action for server-side validation. No drift.
|
|
77
|
+
2. **`noValidate`** — disable browser default validation (which conflicts with custom error UX).
|
|
78
|
+
3. **`role="alert"`** on errors — announces to screen readers immediately.
|
|
79
|
+
4. **`aria-invalid`** — semantic state for AT.
|
|
80
|
+
5. **`form.formState.isSubmitting`** disables the button — no double-submit, no manual `useState` for loading.
|
|
81
|
+
|
|
82
|
+
## Schema sharing with the Server Action
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
// schemas/contact.ts (single source of truth)
|
|
86
|
+
import { z } from 'zod';
|
|
87
|
+
export const ContactSchema = z.object({
|
|
88
|
+
email: z.string().email(),
|
|
89
|
+
message: z.string().min(10).max(2000),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// app/(actions)/contact.ts
|
|
93
|
+
'use server';
|
|
94
|
+
import { ContactSchema } from '@/schemas/contact';
|
|
95
|
+
|
|
96
|
+
export async function saveContact(input: unknown) {
|
|
97
|
+
const parsed = ContactSchema.safeParse(input); // ← same schema
|
|
98
|
+
if (!parsed.success) return { ok: false, error: 'invalid-input' };
|
|
99
|
+
/* ... service call ... */
|
|
100
|
+
return { ok: true };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// components/ContactForm.tsx
|
|
104
|
+
import { ContactSchema } from '@/schemas/contact';
|
|
105
|
+
// resolver: zodResolver(ContactSchema) ← same schema
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Never duplicate the schema in the client and server. Always import from one location.
|
|
109
|
+
|
|
110
|
+
## Error display patterns
|
|
111
|
+
|
|
112
|
+
- **Field-level errors**: render right after the input (`{errors.field && <span role="alert">`).
|
|
113
|
+
- **Form-level errors** (server rejection): use `form.setError('root', ...)` and render once at top or near submit button.
|
|
114
|
+
- **No `alert()`**, no `toast.error()` for validation — inline is more usable and accessible.
|
|
115
|
+
- **Focus the first invalid field on submit fail** — react-hook-form does this by default via `shouldFocusError: true`.
|
|
116
|
+
|
|
117
|
+
## When NOT to use react-hook-form
|
|
118
|
+
|
|
119
|
+
- **Single-field form** (search, newsletter signup):
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
<form action={subscribe}>
|
|
123
|
+
<input name="email" type="email" required />
|
|
124
|
+
<button>Subscribe</button>
|
|
125
|
+
</form>
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Native HTML + Server Action. No library. The browser's `required` + `type="email"` covers basic validation; the Server Action does the real check.
|
|
129
|
+
|
|
130
|
+
- **File upload only** — use `<input type="file">` + Server Action; the form library adds no value.
|
|
131
|
+
|
|
132
|
+
> **Repeatable fields in a native form**: when the Server Action reads `FormData` directly (the paths above — multi-select, a checkbox group, `<input multiple>`), do **not** `Object.fromEntries(formData)`: it keeps only the last value and silently drops the rest. Read repeatable fields with `formData.getAll(name)` and validate them with `z.array(...)` in the shared schema. See `void-server-action`.
|
|
133
|
+
|
|
134
|
+
- **Wizard / multi-step** — react-hook-form's `Controller` works but consider XState or a similar state machine for complex flows.
|
|
135
|
+
|
|
136
|
+
## Anti-patterns
|
|
137
|
+
|
|
138
|
+
- ✗ **`useState` for each field** — that's what react-hook-form replaces; you lose validation, focus management, submit orchestration
|
|
139
|
+
- ✗ **Validation in `onSubmit` only** — users want feedback on blur, not just on submit. Use `mode: 'onBlur'` or `'onTouched'`
|
|
140
|
+
- ✗ **Different schemas client and server** — they drift; bugs slip through. One Zod schema.
|
|
141
|
+
- ✗ **Disabling submit button via `disabled={!form.formState.isValid}`** — confusing UX. Let the user click; show errors after.
|
|
142
|
+
- ✗ **`onSubmit={handler}` directly** — bypasses validation. Always `form.handleSubmit(handler)`.
|
|
143
|
+
- ✗ **Calling `toast.error()` for field validation** — inline errors are the standard a11y pattern.
|
|
144
|
+
|
|
145
|
+
## Touch + mobile
|
|
146
|
+
|
|
147
|
+
- Use `inputMode="email"` / `inputMode="numeric"` / `inputMode="decimal"` to surface the right mobile keyboard.
|
|
148
|
+
- `autoComplete="email"` / `"current-password"` / `"one-time-code"` for the right autofill behavior. Skip = bad UX.
|
|
149
|
+
- Touch targets ≥ 44×44 (composes with `void-accessibility-check`).
|
|
150
|
+
|
|
151
|
+
## Composition
|
|
152
|
+
|
|
153
|
+
- `void-server-action` — the submit target; shares the Zod schema.
|
|
154
|
+
- `void-security-guidance` — Zod schema is the trust boundary; server-side re-validates identically.
|
|
155
|
+
- `void-state-architecture` — form state IS local state; lives in the form component, never lifted.
|
|
156
|
+
- `void-accessibility-check` — labels, role="alert", focus management on error.
|
|
157
|
+
- `void-tdd` — form components get `@testing-library/user-event` tests asserting validation + submit + error display.
|
|
@@ -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-state-architecture` skill (see `docs/plans/skill-audits/void-state-architecture.md` for the full audit):
|
|
2
|
+
|
|
3
|
+
- No external source audited — native skill authored for void-machine.
|
|
4
|
+
|
|
5
|
+
Strategy: native. Encodes a strict tier order (URL > server > local > lifted > Zustand > React Query) against React-specific state tooling that other UI stacks do not share.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: void-state-architecture
|
|
3
|
+
description: Decide where state lives — local, lifted, URL, server, client global. The decision tree to avoid useState-in-the-wrong-place sprawl. Default to URL or server before reaching for global stores.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# state-architecture
|
|
7
|
+
|
|
8
|
+
Use when adding any state to a React app. The wrong location is the most common architectural drift: state ends up too high (every input re-renders the page), too low (sibling components can't communicate), or in a global store when URL would have sufficed.
|
|
9
|
+
|
|
10
|
+
## The decision tree (top → bottom; use the highest that works)
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
1. Can it live in the URL? → URL search params or path segment
|
|
14
|
+
2. Can the server own it? → DB / cache, render via Server Component
|
|
15
|
+
3. Is it ONE component's concern? → useState in that component
|
|
16
|
+
4. Is it ≤ 3 sibling components? → lift to closest common parent
|
|
17
|
+
5. Is it cross-tree client-only? → Zustand (or Jotai for atoms)
|
|
18
|
+
6. Is it server data with caching? → React Query (TanStack) / SWR
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Default to (1) or (2). Reach for (5) last. Context is mentioned below but rarely the right answer.
|
|
22
|
+
|
|
23
|
+
## (1) URL state — the most under-used
|
|
24
|
+
|
|
25
|
+
State that should survive a refresh, be shareable, or be back-button-friendly belongs in the URL:
|
|
26
|
+
|
|
27
|
+
- Filters, sorts, pagination, tabs
|
|
28
|
+
- "Which item is selected" in a list/detail layout
|
|
29
|
+
- Modal open/closed when the modal is shareable (`?invite=true`)
|
|
30
|
+
- Search queries
|
|
31
|
+
|
|
32
|
+
```tsx
|
|
33
|
+
// Server Component reading searchParams
|
|
34
|
+
export default function Page({ searchParams }: { searchParams: { sort?: string } }) {
|
|
35
|
+
const sort = searchParams.sort ?? 'newest';
|
|
36
|
+
return <ItemList sort={sort} />;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Client Component pushing to URL
|
|
40
|
+
'use client';
|
|
41
|
+
import { useRouter, useSearchParams } from 'next/navigation';
|
|
42
|
+
export function SortSelect() {
|
|
43
|
+
const router = useRouter();
|
|
44
|
+
const params = useSearchParams();
|
|
45
|
+
return <select onChange={(e) => router.push(`?${new URLSearchParams({ ...Object.fromEntries(params), sort: e.target.value })}`)}>...</select>;
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The benefit: refresh works, share link works, back button works, server can SSR with the right data. Three lines of effort, huge UX win.
|
|
50
|
+
|
|
51
|
+
## (2) Server state — the second most under-used
|
|
52
|
+
|
|
53
|
+
If the data is in the database (or any server cache), the source of truth is the server. Don't mirror it into `useState`:
|
|
54
|
+
|
|
55
|
+
```tsx
|
|
56
|
+
// ✗ Client component fetching + storing
|
|
57
|
+
'use client';
|
|
58
|
+
export function UserCard({ userId }) {
|
|
59
|
+
const [user, setUser] = useState<User | null>(null);
|
|
60
|
+
useEffect(() => { fetch(`/api/users/${userId}`).then(r => r.json()).then(setUser); }, [userId]);
|
|
61
|
+
if (!user) return <Spinner />;
|
|
62
|
+
return <Card>{user.name}</Card>;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ✓ Server Component fetching directly
|
|
66
|
+
export async function UserCard({ userId }: { userId: string }) {
|
|
67
|
+
const user = await userService.getById(userId);
|
|
68
|
+
return <Card>{user.name}</Card>;
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
When server-fetch isn't feasible (interactive lists with optimistic updates), reach for React Query — but read (1) and (2) first.
|
|
73
|
+
|
|
74
|
+
## (3) Local state — `useState` in one component
|
|
75
|
+
|
|
76
|
+
For state ONE component cares about (input value, dropdown open, hover, focus):
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
const [open, setOpen] = useState(false);
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Fine. Don't lift it. Don't put it in context. Don't put it in Zustand.
|
|
83
|
+
|
|
84
|
+
## (4) Lifted state — closest common parent
|
|
85
|
+
|
|
86
|
+
When 2-3 siblings share state, lift to the closest common parent. NOT to the page root, NOT to a layout, NOT to context. The closest common parent.
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
function TabContainer() { // closest common parent
|
|
90
|
+
const [active, setActive] = useState('overview');
|
|
91
|
+
return (
|
|
92
|
+
<>
|
|
93
|
+
<TabHeader active={active} onChange={setActive} />
|
|
94
|
+
<TabContent active={active} />
|
|
95
|
+
</>
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
If lifting forces you to drill props through 4+ levels, you have a structural problem — not a state location problem. Refactor the component tree first.
|
|
101
|
+
|
|
102
|
+
## (5) Client global state — Zustand / Jotai
|
|
103
|
+
|
|
104
|
+
Use **only** when:
|
|
105
|
+
|
|
106
|
+
- State is genuinely cross-tree (consumed by components that don't share a useful common parent — e.g., a toast queue, a command palette open state)
|
|
107
|
+
- And it's client-side only (no server source of truth)
|
|
108
|
+
- And lifting would cross > 5 component levels
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
// stores/toast.ts
|
|
112
|
+
import { create } from 'zustand';
|
|
113
|
+
export const useToast = create<{ items: Toast[]; push(t: Toast): void; dismiss(id: string): void }>(...);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
If you can't justify ALL THREE conditions, don't add a store. Zustand is light but each store is a new mental load + a new place to debug.
|
|
117
|
+
|
|
118
|
+
## When Context is right
|
|
119
|
+
|
|
120
|
+
- Theme (rarely changes, deeply read)
|
|
121
|
+
- Locale / i18n (same)
|
|
122
|
+
- Auth user (Server Components read it from cookies; Client Components get a snapshot via `<UserProvider>`)
|
|
123
|
+
|
|
124
|
+
Anything that **changes frequently** (form state, list selection, hover) in Context **kills perf**: every consumer re-renders on every change. That's not a Context problem, that's a wrong-tool problem.
|
|
125
|
+
|
|
126
|
+
## Server state caching: React Query vs Server Components
|
|
127
|
+
|
|
128
|
+
If you're on RSC + Server Actions, your "server state" is fetched server-side. React Query becomes useful only for:
|
|
129
|
+
|
|
130
|
+
- Lists with optimistic updates that don't go through a Server Action (rare)
|
|
131
|
+
- Real-time data with explicit refetch needs (e.g., dashboards polling every 5s)
|
|
132
|
+
- Apps that aren't RSC (Vite SPA, Expo)
|
|
133
|
+
|
|
134
|
+
In Next.js App Router with Server Actions, you can mostly skip React Query.
|
|
135
|
+
|
|
136
|
+
## Anti-patterns
|
|
137
|
+
|
|
138
|
+
- ✗ **Storing form values in a Zustand store** — local state in the form component is correct
|
|
139
|
+
- ✗ **Storing server data in `useState`** — leads to stale data, refetch dance
|
|
140
|
+
- ✗ **Putting `filterValue` in Context** — every consumer re-renders on every keystroke
|
|
141
|
+
- ✗ **Using `useState` for what should be in the URL** — refresh loses state, share-link breaks
|
|
142
|
+
- ✗ **Three different stores for the same feature** (`useUserStore`, `useUserPreferencesStore`, `useUserSessionStore`) — fuse or use selectors
|
|
143
|
+
|
|
144
|
+
## Workflow
|
|
145
|
+
|
|
146
|
+
For each state you're about to add:
|
|
147
|
+
|
|
148
|
+
1. **Can it be URL?** If yes, use URL search params.
|
|
149
|
+
2. **Can it be server?** If yes, fetch in a Server Component.
|
|
150
|
+
3. **Does ONE component need it?** `useState` there.
|
|
151
|
+
4. **Do ≤3 siblings need it?** Lift to common parent.
|
|
152
|
+
5. **Genuinely cross-tree client-only?** Zustand store; one per concern.
|
|
153
|
+
6. **Server data with caching needs?** React Query, but exhaust 1+2 first.
|
|
154
|
+
|
|
155
|
+
## Composition
|
|
156
|
+
|
|
157
|
+
- `void-client-vs-server-component` — server state lives in Server Components, client state lives in Client Components.
|
|
158
|
+
- `void-form-pattern` — react-hook-form handles form state; this skill says it's local.
|
|
159
|
+
- `void-offline-first-mutation` — capture-queue uses IndexedDB for offline writes (a 7th tier above Zustand, scoped to writes-pending-sync).
|
|
160
|
+
- `void-cache-component-pattern` — server data caching strategy.
|
|
@@ -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,6 @@
|
|
|
1
|
+
Sources for `void-background-job-pattern` skill (see `docs/plans/skill-audits/void-background-job-pattern.md` for the full audit):
|
|
2
|
+
|
|
3
|
+
- No external source audited — native distillation of the void-machine job taxonomy (event-driven vs cron vs one-shot) and per-type wrapper patterns.
|
|
4
|
+
- Tooling referenced (not vendored): Inngest, Trigger.dev, Cloudflare Queues, Vercel Cron.
|
|
5
|
+
|
|
6
|
+
Strategy: distill. Distilled from the harness's own job-type decision table; queue tooling is named but no source skill was copied.
|