@kensaurus/skills 0.0.0-stage → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/marketplace.json +53 -0
- package/.claude-plugin/plugin.json +40 -0
- package/.cursor-plugin/plugin.json +38 -0
- package/.mcp.json +28 -0
- package/CHANGELOG.md +1790 -0
- package/LICENSE +21 -0
- package/LICENSE-APACHE +62 -0
- package/NOTICE +13 -0
- package/README.md +821 -2
- package/SECURITY.md +55 -0
- package/agents/code-reviewer.md +60 -0
- package/agents/completion-judge.md +89 -0
- package/agents/db-migrator.md +125 -0
- package/agents/debugger.md +47 -0
- package/agents/deploy-checker.md +100 -0
- package/agents/perf-monitor.md +74 -0
- package/assets/favicon.png +0 -0
- package/assets/logo-light.png +0 -0
- package/assets/logo.png +0 -0
- package/assets/logo.svg +6 -0
- package/assets/og.png +0 -0
- package/bin/install.mjs +1127 -0
- package/bin/kenji.js +2 -0
- package/commands/adr.md +17 -0
- package/commands/aeo-plan.md +18 -0
- package/commands/arch-boundaries.md +17 -0
- package/commands/aso-plan.md +18 -0
- package/commands/auth-flows.md +19 -0
- package/commands/backup-plan.md +17 -0
- package/commands/burndown-full.md +25 -0
- package/commands/capacitor-plan.md +18 -0
- package/commands/codemod-safety.md +22 -0
- package/commands/commit.md +18 -0
- package/commands/complete-everything.md +40 -0
- package/commands/cost-plan.md +19 -0
- package/commands/deadcode-plan.md +26 -0
- package/commands/deadcode.md +32 -0
- package/commands/debug-issue.md +17 -0
- package/commands/deps-plan.md +18 -0
- package/commands/docs-plan.md +17 -0
- package/commands/doctrine.md +19 -0
- package/commands/error-plan.md +19 -0
- package/commands/feedback-to-closure.md +36 -0
- package/commands/fix-issue.md +76 -0
- package/commands/gate-logic.md +26 -0
- package/commands/green-repo.md +36 -0
- package/commands/grill-me.md +19 -0
- package/commands/gtm-plan.md +21 -0
- package/commands/gtm-weekly.md +17 -0
- package/commands/gtm.md +22 -0
- package/commands/handoff.md +15 -0
- package/commands/housekeep-backlog.md +18 -0
- package/commands/housekeep-files.md +22 -0
- package/commands/housekeep-gates.md +18 -0
- package/commands/instant-nav.md +11 -0
- package/commands/integrity-plan.md +19 -0
- package/commands/launch-kit.md +16 -0
- package/commands/mcp-guide.md +40 -0
- package/commands/mobile-plan.md +19 -0
- package/commands/native-rn-monorepo/README.md +78 -0
- package/commands/native-rn-monorepo/android-build.md +26 -0
- package/commands/native-rn-monorepo/android-install.md +32 -0
- package/commands/native-rn-monorepo/android-logcat.md +37 -0
- package/commands/native-rn-monorepo/ios-ci-logs.md +56 -0
- package/commands/native-rn-monorepo/ios-ci-status.md +52 -0
- package/commands/native-rn-monorepo/ios-ci-trigger.md +59 -0
- package/commands/native-rn-monorepo/rn-reset.md +50 -0
- package/commands/native-rn-monorepo/rn-ship-ios.md +67 -0
- package/commands/native-rn-monorepo/rn-verify.md +53 -0
- package/commands/perf-plan.md +18 -0
- package/commands/plan-mode.md +74 -0
- package/commands/pr.md +16 -0
- package/commands/pricing-plan.md +20 -0
- package/commands/privacy-plan.md +18 -0
- package/commands/readability.md +12 -0
- package/commands/readme.md +15 -0
- package/commands/refactor.md +15 -0
- package/commands/release-prep.md +17 -0
- package/commands/research.md +25 -0
- package/commands/responsive-audit.md +21 -0
- package/commands/review-code.md +18 -0
- package/commands/rls-plan.md +18 -0
- package/commands/secrets-plan.md +18 -0
- package/commands/security-plan.md +19 -0
- package/commands/ship-and-observe.md +36 -0
- package/commands/skill-conflicts.md +19 -0
- package/commands/slop-plan.md +18 -0
- package/commands/stub-plan.md +18 -0
- package/commands/test-mutation.md +16 -0
- package/commands/test-plan.md +17 -0
- package/commands/test.md +29 -0
- package/commands/thirdparty-web-interface-guidelines.md +185 -0
- package/commands/uiux-plan.md +18 -0
- package/commands/uiux.md +45 -0
- package/commands/update-deps.md +21 -0
- package/commands/validation-plan.md +19 -0
- package/commands-portable/fix-issue.md +72 -0
- package/commands-portable/plan-mode.md +92 -0
- package/commands-portable/research.md +91 -0
- package/docs/screenshots/README.md +5 -0
- package/docs/screenshots/audit-dark.png +0 -0
- package/docs/screenshots/build-dark.png +0 -0
- package/docs/screenshots/grill-dark.png +0 -0
- package/docs/screenshots/hero-dark.png +0 -0
- package/docs/screenshots/hero-light.png +0 -0
- package/docs/screenshots/ship-dark.png +0 -0
- package/docs/screenshots/src/showcase.html +320 -0
- package/hooks/completion-gate.mjs +258 -0
- package/hooks/cursor-hooks.json +13 -0
- package/hooks/hooks.json +15 -0
- package/install.sh +21 -0
- package/llms.txt +40 -0
- package/mcp/README.md +266 -0
- package/mcp/VERSIONS.md +41 -0
- package/mcp/mcp-full.json.template +124 -0
- package/mcp/mcp.json.template +29 -0
- package/mcp/pinned-versions.json +27 -0
- package/package.json +94 -4
- package/rules/approved-plan-execution.mdc +65 -0
- package/rules/full-stack-ship-discipline.mdc +37 -0
- package/rules/native-rn-monorepo/README.md +63 -0
- package/rules/native-rn-monorepo/_project.mdc +69 -0
- package/rules/native-rn-monorepo/native-android.mdc +72 -0
- package/rules/native-rn-monorepo/native-ios.mdc +61 -0
- package/rules/native-rn-monorepo/react-native-js.mdc +78 -0
- package/rules/native-rn-monorepo/web.mdc +60 -0
- package/rules/project-starter/components.mdc +54 -0
- package/rules/project-starter/data-fetching.mdc +77 -0
- package/rules/project-starter/git.mdc +41 -0
- package/rules/project-starter/supabase.mdc +37 -0
- package/rules/project-starter/tailwind.mdc +48 -0
- package/rules/project-starter/typescript.mdc +36 -0
- package/rules/project-starter/web-performance.mdc +42 -0
- package/rules/senior-engineer.mdc +30 -0
- package/rules/shell-first-search.mdc +19 -0
- package/rules/skill-workflows.mdc +35 -0
- package/rules/verification-before-completion.mdc +57 -0
- package/skills/audit-accessibility/SKILL.md +441 -0
- package/skills/audit-agent-speed/SKILL.md +181 -0
- package/skills/audit-agent-speed/scripts/stop-typecheck.mjs +151 -0
- package/skills/audit-analytics/SKILL.md +138 -0
- package/skills/audit-auth-flows/SKILL.md +267 -0
- package/skills/audit-backend-architecture/SKILL.md +266 -0
- package/skills/audit-backend-architecture/references/patterns.md +386 -0
- package/skills/audit-bundle-size/SKILL.md +295 -0
- package/skills/audit-cicd/SKILL.md +218 -0
- package/skills/audit-code-quality/SKILL.md +314 -0
- package/skills/audit-code-review/SKILL.md +289 -0
- package/skills/audit-codemod-safety/SKILL.md +159 -0
- package/skills/audit-db-schema/SKILL.md +465 -0
- package/skills/audit-db-schema/references/details.md +110 -0
- package/skills/audit-doctrine/SKILL.md +189 -0
- package/skills/audit-env-parity/SKILL.md +133 -0
- package/skills/audit-fe-api/SKILL.md +458 -0
- package/skills/audit-gate-logic/SKILL.md +219 -0
- package/skills/audit-i18n/SKILL.md +339 -0
- package/skills/audit-infra-cost/SKILL.md +142 -0
- package/skills/audit-langfuse-llm/SKILL.md +468 -0
- package/skills/audit-langfuse-llm/references/details.md +226 -0
- package/skills/audit-llm-security/SKILL.md +147 -0
- package/skills/audit-monetization-iap/SKILL.md +137 -0
- package/skills/audit-payment-system/SKILL.md +268 -0
- package/skills/audit-payment-system/references/checklist.md +283 -0
- package/skills/audit-performance/SKILL.md +383 -0
- package/skills/audit-performance/references/loading-priority-2026.md +81 -0
- package/skills/audit-realworld/SKILL.md +287 -0
- package/skills/audit-registry-listing/SKILL.md +122 -0
- package/skills/audit-resilience/SKILL.md +153 -0
- package/skills/audit-responsive/SKILL.md +221 -0
- package/skills/audit-responsive/references/checklist.md +166 -0
- package/skills/audit-security/SKILL.md +289 -0
- package/skills/audit-skill-conflicts/SKILL.md +178 -0
- package/skills/audit-ui-states/SKILL.md +146 -0
- package/skills/audit-uiux-design-system/SKILL.md +475 -0
- package/skills/audit-uiux-design-system/references/details.md +71 -0
- package/skills/audit-ux/SKILL.md +381 -0
- package/skills/audit-ux/references/details.md +245 -0
- package/skills/audit-ux-journeys/SKILL.md +215 -0
- package/skills/audit-ux-journeys/references/checklist.md +179 -0
- package/skills/backend-db-performance/SKILL.md +441 -0
- package/skills/backend-error-handling/SKILL.md +489 -0
- package/skills/backend-error-handling/references/details.md +58 -0
- package/skills/backend-observability/SKILL.md +88 -0
- package/skills/backend-patterns/SKILL.md +499 -0
- package/skills/backend-patterns/references/architecture-patterns.md +298 -0
- package/skills/backend-realtime/SKILL.md +403 -0
- package/skills/backend-realtime/references/patterns.md +74 -0
- package/skills/burndown-full/SKILL.md +174 -0
- package/skills/complete-everything/SKILL.md +295 -0
- package/skills/data-pipeline/SKILL.md +109 -0
- package/skills/data-visualization/SKILL.md +488 -0
- package/skills/debug-error/SKILL.md +322 -0
- package/skills/debug-fe-be-integration/SKILL.md +459 -0
- package/skills/debug-sentry-monitor/SKILL.md +497 -0
- package/skills/debug-sentry-monitor/references/details.md +165 -0
- package/skills/deploy-npm/SKILL.md +394 -0
- package/skills/deploy-npm/references/example-mushi-mushi.md +52 -0
- package/skills/deploy-verify/SKILL.md +489 -0
- package/skills/design-api/SKILL.md +379 -0
- package/skills/design-canvas/SKILL.md +155 -0
- package/skills/design-email/SKILL.md +370 -0
- package/skills/design-frontend/SKILL.md +143 -0
- package/skills/design-generative-art/SKILL.md +474 -0
- package/skills/design-mobile-first/SKILL.md +506 -0
- package/skills/design-motion/SKILL.md +333 -0
- package/skills/design-motion/references/delight-interactions.md +191 -0
- package/skills/design-prd/SKILL.md +443 -0
- package/skills/design-system/SKILL.md +457 -0
- package/skills/design-theme/SKILL.md +226 -0
- package/skills/design-theme/themes/tsumagoi-ranch.md +150 -0
- package/skills/docs-adr/SKILL.md +168 -0
- package/skills/docs-coauthor/SKILL.md +368 -0
- package/skills/docs-comparison-pages/SKILL.md +117 -0
- package/skills/docs-domain-modeling/SKILL.md +97 -0
- package/skills/docs-launch-kit/SKILL.md +139 -0
- package/skills/docs-writer/SKILL.md +469 -0
- package/skills/enhance-agent-guardrails/SKILL.md +164 -0
- package/skills/enhance-arch-boundaries/SKILL.md +154 -0
- package/skills/enhance-capacitor-ui/SKILL.md +463 -0
- package/skills/enhance-capacitor-ui/references/details.md +750 -0
- package/skills/enhance-email-deliverability/SKILL.md +143 -0
- package/skills/enhance-growth-loops/SKILL.md +122 -0
- package/skills/enhance-lifecycle-email/SKILL.md +130 -0
- package/skills/enhance-motion/SKILL.md +193 -0
- package/skills/enhance-onboarding/SKILL.md +148 -0
- package/skills/enhance-pwa/SKILL.md +303 -0
- package/skills/enhance-readability/SKILL.md +146 -0
- package/skills/enhance-readme/SKILL.md +496 -0
- package/skills/enhance-readme/package-lock.json +187 -0
- package/skills/enhance-readme/package.json +17 -0
- package/skills/enhance-readme/scripts/generate-readme-blocks.mjs +199 -0
- package/skills/enhance-readme/scripts/record-readme-tour.mjs +442 -0
- package/skills/enhance-skill-prompts/SKILL.md +167 -0
- package/skills/enhance-skill-prompts/references/exemplar-audit-auth-flows.md +311 -0
- package/skills/enhance-ux-laws/SKILL.md +409 -0
- package/skills/enhance-web-conversion/SKILL.md +155 -0
- package/skills/enhance-web-forms/SKILL.md +154 -0
- package/skills/enhance-web-instant-nav/SKILL.md +138 -0
- package/skills/enhance-web-instant-nav/references/bfcache-blockers.md +23 -0
- package/skills/enhance-web-instant-nav/references/early-hints.md +33 -0
- package/skills/enhance-web-instant-nav/references/speculation-rules.md +44 -0
- package/skills/enhance-web-landing/SKILL.md +459 -0
- package/skills/enhance-web-landing/references/details.md +773 -0
- package/skills/enhance-web-redesign/SKILL.md +228 -0
- package/skills/enhance-web-seo/SKILL.md +276 -0
- package/skills/enhance-web-ui/SKILL.md +473 -0
- package/skills/enhance-web-ui/references/details.md +674 -0
- package/skills/enhance-web-ux/HEURISTICS.md +248 -0
- package/skills/enhance-web-ux/PATTERNS.md +375 -0
- package/skills/enhance-web-ux/SKILL.md +464 -0
- package/skills/enhance-web-ux/examples.md +222 -0
- package/skills/enhance-web-ux/references/details.md +406 -0
- package/skills/enhance-web-web3d/SKILL.md +397 -0
- package/skills/enhance-web-web3d/references/css-canvas-effects.md +180 -0
- package/skills/handoff/SKILL.md +66 -0
- package/skills/housekeep-backlog/SKILL.md +149 -0
- package/skills/housekeep-dead-code/SKILL.md +387 -0
- package/skills/housekeep-dead-code/references/ratchet-ci.md +205 -0
- package/skills/housekeep-dead-code/references/supabase-hygiene.md +152 -0
- package/skills/housekeep-design/SKILL.md +207 -0
- package/skills/housekeep-files/SKILL.md +220 -0
- package/skills/housekeep-files/references/naming-and-catalog.md +86 -0
- package/skills/housekeep-files/scripts/housekeep-files.ps1 +360 -0
- package/skills/housekeep-files/scripts/housekeep-files.sh +238 -0
- package/skills/housekeep-gates/SKILL.md +174 -0
- package/skills/iterate-agent-harness/SKILL.md +137 -0
- package/skills/iterate-gtm-weekly/SKILL.md +103 -0
- package/skills/iterate-post-launch/SKILL.md +292 -0
- package/skills/meta-mcp-builder/SKILL.md +313 -0
- package/skills/meta-skill-creator/SKILL.md +304 -0
- package/skills/mobile-capacitor-platform/SKILL.md +103 -0
- package/skills/mobile-emulator-start/SKILL.md +296 -0
- package/skills/mobile-emulator-test/SKILL.md +491 -0
- package/skills/mobile-emulator-test/references/details.md +478 -0
- package/skills/mobile-rn-performance/SKILL.md +107 -0
- package/skills/mobile-rn-screen/SKILL.md +476 -0
- package/skills/mobile-rn-screen/references/details.md +785 -0
- package/skills/mushi-health/SKILL.md +206 -0
- package/skills/mushi-integration/SKILL.md +257 -0
- package/skills/plan-aeo-readiness/SKILL.md +166 -0
- package/skills/plan-antislop/SKILL.md +281 -0
- package/skills/plan-aso/SKILL.md +149 -0
- package/skills/plan-backup-dr/SKILL.md +131 -0
- package/skills/plan-capacitor-hardening/SKILL.md +217 -0
- package/skills/plan-data-integrity/SKILL.md +187 -0
- package/skills/plan-dead-code/SKILL.md +386 -0
- package/skills/plan-dead-code/references/knip-config.md +214 -0
- package/skills/plan-dead-code/references/output-templates.md +133 -0
- package/skills/plan-dead-code/references/preservation-contract.md +50 -0
- package/skills/plan-dead-code/references/residue-greps.md +84 -0
- package/skills/plan-dependency-provenance/SKILL.md +200 -0
- package/skills/plan-docs-sync/SKILL.md +143 -0
- package/skills/plan-docs-sync/references/drift-taxonomy.md +43 -0
- package/skills/plan-docs-sync/references/output-templates.md +33 -0
- package/skills/plan-docs-sync/references/preservation-contract.md +17 -0
- package/skills/plan-error-handling/SKILL.md +205 -0
- package/skills/plan-gtm/SKILL.md +276 -0
- package/skills/plan-gtm/references/benchmarks-2026.md +183 -0
- package/skills/plan-input-validation/SKILL.md +179 -0
- package/skills/plan-llm-cost-guardrails/SKILL.md +176 -0
- package/skills/plan-mobile-readiness/SKILL.md +171 -0
- package/skills/plan-perf-audit/SKILL.md +145 -0
- package/skills/plan-perf-audit/references/audit-scope.md +51 -0
- package/skills/plan-perf-audit/references/output-templates.md +33 -0
- package/skills/plan-perf-audit/references/preservation-contract.md +13 -0
- package/skills/plan-pricing/SKILL.md +173 -0
- package/skills/plan-privacy-compliance/SKILL.md +148 -0
- package/skills/plan-rls-audit/SKILL.md +231 -0
- package/skills/plan-secrets-audit/SKILL.md +181 -0
- package/skills/plan-security-audit/SKILL.md +168 -0
- package/skills/plan-security-audit/references/output-templates.md +36 -0
- package/skills/plan-security-audit/references/owasp-supabase-scope.md +55 -0
- package/skills/plan-security-audit/references/preservation-contract.md +18 -0
- package/skills/plan-stub-checker/SKILL.md +216 -0
- package/skills/plan-stub-checker/references/detection-methodology.md +75 -0
- package/skills/plan-stub-checker/references/detection-taxonomy.md +34 -0
- package/skills/plan-stub-checker/references/output-templates.md +63 -0
- package/skills/plan-stub-checker/references/preservation-contract.md +24 -0
- package/skills/plan-test-coverage/SKILL.md +170 -0
- package/skills/plan-test-coverage/references/methodology.md +54 -0
- package/skills/plan-test-coverage/references/output-templates.md +34 -0
- package/skills/plan-test-coverage/references/preservation-contract.md +15 -0
- package/skills/plan-uiux-unification/SKILL.md +230 -0
- package/skills/plan-uiux-unification/references/output-templates.md +67 -0
- package/skills/plan-uiux-unification/references/phase-workbook.md +85 -0
- package/skills/plan-uiux-unification/references/preservation-contract.md +24 -0
- package/skills/protocol-browser-anti-stall/SKILL.md +211 -0
- package/skills/protocol-browser-anti-stall/references/mcp-to-cli-map.md +113 -0
- package/skills/protocol-browser-anti-stall/references/playwright-session-coordination.md +170 -0
- package/skills/research/SKILL.md +422 -0
- package/skills/test-exploratory/SKILL.md +165 -0
- package/skills/test-exploratory/references/charter-template.md +29 -0
- package/skills/test-load/SKILL.md +126 -0
- package/skills/test-mutation/SKILL.md +160 -0
- package/skills/test-playwright/SKILL.md +354 -0
- package/skills/test-qa/SKILL.md +364 -0
- package/skills/test-qa/references/details.md +268 -0
- package/skills/test-red-team/SKILL.md +386 -0
- package/skills/test-red-team/references/owasp-attack-checklist.md +193 -0
- package/skills/test-unit/SKILL.md +259 -0
- package/skills/test-unit/references/details.md +267 -0
- package/skills/test-visual-regression/SKILL.md +132 -0
- package/skills/thirdparty-emil-design-eng/ATTRIBUTION.md +20 -0
- package/skills/thirdparty-emil-design-eng/SKILL.md +21 -0
- package/skills/thirdparty-emil-design-eng/references/emil-design-eng.md +676 -0
- package/skills/thirdparty-ui-ux-pro-max/ATTRIBUTION.md +22 -0
- package/skills/thirdparty-ui-ux-pro-max/SKILL.md +304 -0
- package/skills/thirdparty-ui-ux-pro-max/data/charts.csv +26 -0
- package/skills/thirdparty-ui-ux-pro-max/data/colors.csv +97 -0
- package/skills/thirdparty-ui-ux-pro-max/data/icons.csv +101 -0
- package/skills/thirdparty-ui-ux-pro-max/data/landing.csv +31 -0
- package/skills/thirdparty-ui-ux-pro-max/data/products.csv +97 -0
- package/skills/thirdparty-ui-ux-pro-max/data/react-performance.csv +45 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/astro.csv +54 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/skills/thirdparty-ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/skills/thirdparty-ui-ux-pro-max/data/styles.csv +68 -0
- package/skills/thirdparty-ui-ux-pro-max/data/typography.csv +58 -0
- package/skills/thirdparty-ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/skills/thirdparty-ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/skills/thirdparty-ui-ux-pro-max/data/web-interface.csv +31 -0
- package/skills/thirdparty-ui-ux-pro-max/scripts/core.py +253 -0
- package/skills/thirdparty-ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/skills/thirdparty-ui-ux-pro-max/scripts/search.py +114 -0
- package/skills/thirdparty-web-interface-guidelines/ATTRIBUTION.md +23 -0
- package/skills/thirdparty-web-interface-guidelines/SKILL.md +190 -0
- package/skills/workflow-build-feature/SKILL.md +118 -0
- package/skills/workflow-coding-discipline/SKILL.md +140 -0
- package/skills/workflow-environment-ready/SKILL.md +128 -0
- package/skills/workflow-feature-flag/SKILL.md +262 -0
- package/skills/workflow-feedback-to-closure/SKILL.md +165 -0
- package/skills/workflow-fix-and-ship/SKILL.md +136 -0
- package/skills/workflow-git-commit/SKILL.md +200 -0
- package/skills/workflow-green-repo/SKILL.md +166 -0
- package/skills/workflow-grilling/SKILL.md +73 -0
- package/skills/workflow-gtm/SKILL.md +153 -0
- package/skills/workflow-housekeep/SKILL.md +453 -0
- package/skills/workflow-housekeep/references/templates.md +109 -0
- package/skills/workflow-launch-ready/SKILL.md +145 -0
- package/skills/workflow-merge-conflicts/SKILL.md +62 -0
- package/skills/workflow-onboard/SKILL.md +99 -0
- package/skills/workflow-parallel-agents/SKILL.md +164 -0
- package/skills/workflow-pr/SKILL.md +197 -0
- package/skills/workflow-quality-gate/SKILL.md +147 -0
- package/skills/workflow-refactor/SKILL.md +274 -0
- package/skills/workflow-release-prep/SKILL.md +207 -0
- package/skills/workflow-ship-and-observe/SKILL.md +164 -0
- package/skills/workflow-spec-tdd/SKILL.md +141 -0
- package/skills/workflow-spec-tdd/references/spec-template.md +126 -0
- package/skills/workflow-spec-tdd/references/tdd-patterns.md +167 -0
- package/skills-cursor/babysit/SKILL.md +17 -0
- package/skills-cursor/canvas/SKILL.md +142 -0
- package/skills-cursor/canvas/sdk/canvas-tokens.d.ts +235 -0
- package/skills-cursor/canvas/sdk/chart-primitives.d.ts +200 -0
- package/skills-cursor/canvas/sdk/dag-layout.d.ts +102 -0
- package/skills-cursor/canvas/sdk/diff-view.d.ts +130 -0
- package/skills-cursor/canvas/sdk/form-primitives.d.ts +194 -0
- package/skills-cursor/canvas/sdk/hooks.d.ts +117 -0
- package/skills-cursor/canvas/sdk/index.d.ts +47 -0
- package/skills-cursor/canvas/sdk/theme.d.ts +61 -0
- package/skills-cursor/canvas/sdk/todo-list.d.ts +49 -0
- package/skills-cursor/canvas/sdk/ui-primitives.d.ts +549 -0
- package/skills-cursor/canvas/sdk/ui-primitives.test.d.ts +2 -0
- package/skills-cursor/create-hook/SKILL.md +238 -0
- package/skills-cursor/create-rule/SKILL.md +185 -0
- package/skills-cursor/create-skill/SKILL.md +269 -0
- package/skills-cursor/create-skill/references/authoring-guide.md +182 -0
- package/skills-cursor/create-subagent/SKILL.md +228 -0
- package/skills-cursor/migrate-to-skills/SKILL.md +121 -0
- package/skills-cursor/shell/SKILL.md +22 -0
- package/skills-cursor/split-to-prs/SKILL.md +47 -0
- package/skills-cursor/statusline/SKILL.md +193 -0
- package/skills-cursor/update-cli-config/SKILL.md +85 -0
- package/skills-cursor/update-cursor-settings/SKILL.md +137 -0
- package/skills.sh.json +297 -0
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
# Architecture patterns — implementation reference
|
|
2
|
+
|
|
3
|
+
Implementation guidance for the distributed-systems patterns `audit-backend-architecture` flags as
|
|
4
|
+
Missing/Partial. Examples lean on TypeScript/Node + Postgres; the patterns are stack-agnostic — adapt
|
|
5
|
+
the client libraries and broker. **Implement the pattern that fits your topology tier — don't add a
|
|
6
|
+
service mesh to a monolith or CQRS where reads and writes don't diverge.**
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## API gateway — centralize cross-cutting concerns
|
|
11
|
+
|
|
12
|
+
The goal is *one* place for auth, rate-limit, CORS, logging, and caching — not copy-paste per route.
|
|
13
|
+
|
|
14
|
+
**Serverless / monolith (T1):** a single middleware chain.
|
|
15
|
+
```ts
|
|
16
|
+
// middleware.ts (Next.js) — or one Express/Hono middleware stack
|
|
17
|
+
export async function middleware(req: NextRequest) {
|
|
18
|
+
// 1. CORS (one policy, not per-route '*')
|
|
19
|
+
// 2. Auth: verify JWT/session once, attach identity
|
|
20
|
+
// 3. Rate limit per client (see Rate Limiting section)
|
|
21
|
+
// 4. Structured request log with a correlation id
|
|
22
|
+
const requestId = crypto.randomUUID();
|
|
23
|
+
const res = NextResponse.next({ request: { headers: withRequestId(req.headers, requestId) } });
|
|
24
|
+
res.headers.set("x-request-id", requestId);
|
|
25
|
+
return res;
|
|
26
|
+
}
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Containers / microservices (T2+):** a dedicated gateway (Kong, APISIX, Traefik, Envoy, or a managed
|
|
30
|
+
API gateway). The gateway owns policy; **it does not reshape payloads** — that is the BFF's job.
|
|
31
|
+
|
|
32
|
+
Checklist: auth, rate-limit, CORS, request/response transform, structured logging with correlation
|
|
33
|
+
id, metrics, and cache headers are all configured **once** and inherited by every route.
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## BFF — Backend-for-Frontend
|
|
38
|
+
|
|
39
|
+
A per-client layer that shapes/aggregates data for one surface. Sits **behind** the gateway; holds
|
|
40
|
+
only client-specific logic (never auth/throttling — that's the gateway).
|
|
41
|
+
|
|
42
|
+
```ts
|
|
43
|
+
// bff/mobile/home.ts — compact payload for a small screen / limited bandwidth
|
|
44
|
+
export async function mobileHome(userId: string) {
|
|
45
|
+
const [user, orders, promos] = await Promise.all([ // fan-out, then reshape
|
|
46
|
+
userService.get(userId),
|
|
47
|
+
orderService.recent(userId, { limit: 3 }), // mobile needs 3, web needs 20
|
|
48
|
+
promoService.forUser(userId),
|
|
49
|
+
]);
|
|
50
|
+
return { // return exactly what mobile renders
|
|
51
|
+
name: user.displayName,
|
|
52
|
+
recentOrders: orders.map((o) => ({ id: o.id, total: o.total, status: o.status })),
|
|
53
|
+
promo: promos[0]?.headline ?? null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
- One BFF per client type (web / mobile / partner). Batch to avoid N+1.
|
|
58
|
+
- **At 5+ domain teams contributing to one graph**, prefer **GraphQL federation** (Apollo Router /
|
|
59
|
+
Cosmo) over N hand-rolled BFFs.
|
|
60
|
+
- Skip a BFF when all clients need the same shape, or only one client exists.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Bulkhead — isolate resource pools
|
|
65
|
+
|
|
66
|
+
One slow dependency must not exhaust the pool every other dependency shares.
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
import pLimit from "p-limit";
|
|
70
|
+
|
|
71
|
+
// A separate concurrency budget PER downstream dependency.
|
|
72
|
+
const limits = {
|
|
73
|
+
payments: pLimit(5), // slow 3rd-party: cap concurrent calls
|
|
74
|
+
search: pLimit(20),
|
|
75
|
+
email: pLimit(10),
|
|
76
|
+
};
|
|
77
|
+
export const callPayments = <T>(fn: () => Promise<T>) => limits.payments(fn);
|
|
78
|
+
```
|
|
79
|
+
- Give each downstream its **own** HTTP client / connection pool, not one global client.
|
|
80
|
+
- Combine with a circuit breaker (below) and per-call timeouts (see `audit-resilience`).
|
|
81
|
+
- Infra tier: separate thread pools / node pools; at scale this becomes cell-based isolation.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Circuit breaker (architectural placement)
|
|
86
|
+
|
|
87
|
+
Wrap every synchronous external dependency. (Per-call tuning: `audit-resilience`.)
|
|
88
|
+
```ts
|
|
89
|
+
import CircuitBreaker from "opossum";
|
|
90
|
+
const breaker = new CircuitBreaker(callProvider, {
|
|
91
|
+
timeout: 3000, errorThresholdPercentage: 50, resetTimeout: 10_000,
|
|
92
|
+
});
|
|
93
|
+
breaker.fallback(() => cachedOrDegradedResponse()); // fail fast, don't hang
|
|
94
|
+
export const chargeCard = (args) => breaker.fire(args);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## Outbox + relay/CDC — kill the dual-write problem
|
|
100
|
+
|
|
101
|
+
Never `save(x)` then `broker.publish(evt)` as two separate ops — a crash between them leaves state
|
|
102
|
+
without its event. Write both in **one transaction**; publish from the outbox afterward.
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
// 1. Write business state + event in the SAME transaction
|
|
106
|
+
await db.transaction(async (tx) => {
|
|
107
|
+
await tx.order.update({ where: { id }, data: { status: "CONFIRMED" } });
|
|
108
|
+
await tx.outbox.create({ data: {
|
|
109
|
+
aggregate: "order", type: "OrderConfirmed", payload: { id }, createdAt: new Date(),
|
|
110
|
+
}});
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
```ts
|
|
114
|
+
// 2a. Relay: poll the outbox and publish (simple, ~20–50ms added latency)
|
|
115
|
+
setInterval(async () => {
|
|
116
|
+
const rows = await db.outbox.findMany({ where: { sentAt: null }, take: 100, orderBy: { createdAt: "asc" }});
|
|
117
|
+
for (const row of rows) {
|
|
118
|
+
await broker.publish(row.type, row.payload);
|
|
119
|
+
await db.outbox.update({ where: { id: row.id }, data: { sentAt: new Date() } });
|
|
120
|
+
}
|
|
121
|
+
}, 200);
|
|
122
|
+
```
|
|
123
|
+
```sql
|
|
124
|
+
-- 2b. Higher throughput: Debezium CDC reads the WAL — no polling load on the DB.
|
|
125
|
+
-- outbox table + logical replication → Kafka Connect (Debezium) → topic
|
|
126
|
+
```
|
|
127
|
+
- Make consumers **idempotent** (dedupe on a message id, 24–72h window) — delivery is at-least-once.
|
|
128
|
+
- This is the reliable substrate for saga step events.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Saga — distributed transactions without 2PC
|
|
133
|
+
|
|
134
|
+
A sequence of local transactions, each emitting an event that triggers the next; **compensating**
|
|
135
|
+
transactions undo prior steps on failure.
|
|
136
|
+
|
|
137
|
+
**Choreography** (<5 linear steps, services already publish events):
|
|
138
|
+
```
|
|
139
|
+
OrderService --OrderCreated--> PaymentService --PaymentCaptured--> ShippingService
|
|
140
|
+
^-- compensate: CancelOrder <-- PaymentFailed / ShippingFailed
|
|
141
|
+
```
|
|
142
|
+
**Orchestration** (branching, per-step timeout/retry, synchronous result) — use Temporal / Step
|
|
143
|
+
Functions / Camel:
|
|
144
|
+
```ts
|
|
145
|
+
// Temporal-style workflow: forward steps + compensation, one readable definition
|
|
146
|
+
export async function bookTrip(input) {
|
|
147
|
+
const flight = await reserveFlight(input); // compensatable
|
|
148
|
+
try {
|
|
149
|
+
const hotel = await reserveHotel(input); // compensatable
|
|
150
|
+
try {
|
|
151
|
+
await capturePayment(input); // ── PIVOT: irreversible ──
|
|
152
|
+
await sendConfirmation(input); // retriable (retry, never compensate)
|
|
153
|
+
await addLoyaltyPoints(input); // retriable
|
|
154
|
+
} catch (e) { /* past pivot: RETRY to completion, do NOT refund */ throw e; }
|
|
155
|
+
} catch (e) { await cancelHotel(input); await cancelFlight(input); throw e; }
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
- **Saga pivot rule:** compensatable steps *before* the pivot (e.g. payment capture); retriable steps
|
|
159
|
+
*after*. Once past the pivot, run to completion with retries — don't compensate.
|
|
160
|
+
- Compensation is business logic — design it as carefully as the happy path.
|
|
161
|
+
- Observe saga age, compensation rate, retry counts, DLQ; alert on stranded/stuck sagas.
|
|
162
|
+
- Pair with **Outbox** so each step's event reliably publishes.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Hexagonal / ports-and-adapters — testable, framework-agnostic
|
|
167
|
+
|
|
168
|
+
Domain logic depends on **ports** (interfaces), never on the framework/DB/HTTP directly. Adapters
|
|
169
|
+
implement the ports at the edges.
|
|
170
|
+
|
|
171
|
+
```ts
|
|
172
|
+
// domain/ports.ts — the domain owns the interface, not the vendor
|
|
173
|
+
export interface OrderRepository { save(o: Order): Promise<void>; byId(id: string): Promise<Order | null>; }
|
|
174
|
+
|
|
175
|
+
// domain/usecase.ts — pure business logic, ZERO framework imports
|
|
176
|
+
export function confirmOrder(repo: OrderRepository, clock: Clock) {
|
|
177
|
+
return async (id: string) => {
|
|
178
|
+
const order = await repo.byId(id);
|
|
179
|
+
if (!order) throw new OrderNotFound(id);
|
|
180
|
+
order.confirm(clock.now()); // business rule lives in the domain
|
|
181
|
+
await repo.save(order);
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// infrastructure/prisma-order-repo.ts — adapter; the ONLY place Prisma is imported
|
|
186
|
+
export class PrismaOrderRepository implements OrderRepository { /* ... */ }
|
|
187
|
+
```
|
|
188
|
+
- Test use-cases with an in-memory adapter — no DB needed.
|
|
189
|
+
- Swap Prisma→Drizzle, Express→Hono, REST→gRPC by writing a new adapter; the domain is untouched.
|
|
190
|
+
- Red flag to fix: `import prisma`/`supabase`/`fetch` inside `domain/` or `application/` layers.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Anti-corruption layer (ACL)
|
|
195
|
+
|
|
196
|
+
Translate external/legacy shapes into your domain at one boundary, so vendor quirks never leak in.
|
|
197
|
+
```ts
|
|
198
|
+
// infrastructure/stripe-acl.ts — map Stripe's shape to OUR domain type here, once
|
|
199
|
+
export function toPayment(stripeCharge: Stripe.Charge): Payment {
|
|
200
|
+
return { id: stripeCharge.id, amount: stripeCharge.amount / 100, currency: stripeCharge.currency };
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Strangler-fig migration
|
|
207
|
+
|
|
208
|
+
Replace a legacy system incrementally behind a façade — route slices to the new implementation, verify,
|
|
209
|
+
repeat — instead of a big-bang rewrite. Gate cutover with `workflow-feature-flag`; keep old+new behind
|
|
210
|
+
one router until every slice is migrated, then decommission.
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
## Communication style — sync request/response vs async event-driven
|
|
215
|
+
|
|
216
|
+
Choose **per interaction**, not per system. Decision tree:
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
Is the caller waiting for the answer?
|
|
220
|
+
├── Yes → SYNC
|
|
221
|
+
│ ├── internal, high-throughput → gRPC
|
|
222
|
+
│ ├── external / simple / cacheable → REST
|
|
223
|
+
│ └── client picks fields / many sources → GraphQL
|
|
224
|
+
└── No → ASYNC
|
|
225
|
+
├── one consumer, do-this-work → message queue (SQS/RabbitMQ/BullMQ)
|
|
226
|
+
├── many reactors, this-happened → event stream (Kafka/NATS/EventBridge)
|
|
227
|
+
└── multi-service transaction → saga (+ outbox)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
// Hybrid in one endpoint: sync at the edge, async for side effects (bridged by the outbox).
|
|
232
|
+
export async function placeOrder(req) {
|
|
233
|
+
const order = await db.transaction(async (tx) => {
|
|
234
|
+
const o = await tx.order.create({ data: req }); // sync: caller needs the orderId now
|
|
235
|
+
await tx.outbox.create({ data: { type: "OrderPlaced", payload: { id: o.id } } });
|
|
236
|
+
return o;
|
|
237
|
+
});
|
|
238
|
+
return { orderId: order.id }; // respond immediately
|
|
239
|
+
// email, inventory, analytics react to OrderPlaced asynchronously — caller doesn't wait
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
- Always give async flows a **dead-letter queue**; give sync calls a **timeout + circuit breaker**.
|
|
243
|
+
- Symptom to fix: one handler making 5 blocking downstream calls — parallelize (`Promise.all`) or move
|
|
244
|
+
non-critical hops to events.
|
|
245
|
+
|
|
246
|
+
## Cache-aside (lazy loading)
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
async function getProduct(id: string): Promise<Product> {
|
|
250
|
+
const key = `product:${id}`;
|
|
251
|
+
const hit = await redis.get<Product>(key);
|
|
252
|
+
if (hit) return hit; // ~10ms
|
|
253
|
+
const product = await db.product.findUnique({ where: { id } }); // ~100ms on miss
|
|
254
|
+
if (product) await redis.set(key, product, { ex: 300 }); // TTL is mandatory
|
|
255
|
+
return product;
|
|
256
|
+
}
|
|
257
|
+
// Invalidate on write — the hard part. Delete (or rewrite) the key when the row changes:
|
|
258
|
+
async function updateProduct(id: string, data: Partial<Product>) {
|
|
259
|
+
const p = await db.product.update({ where: { id }, data });
|
|
260
|
+
await redis.del(`product:${id}`); // or set the fresh value
|
|
261
|
+
return p;
|
|
262
|
+
}
|
|
263
|
+
```
|
|
264
|
+
- **TTL + explicit invalidation** on every cached entity — never cache-forever.
|
|
265
|
+
- Guard hot keys against **stampede/thundering-herd** (single-flight lock or jittered TTL).
|
|
266
|
+
- Never cache private/user-scoped data under a shared key; write to cache **after** the DB commit.
|
|
267
|
+
- Good fit: product details, profiles, config, read-heavy reference data. Bad fit: strongly-consistent
|
|
268
|
+
balances, anything that must never be stale.
|
|
269
|
+
|
|
270
|
+
## Database-per-service / data ownership
|
|
271
|
+
|
|
272
|
+
The rule that makes independent deploy/scale real: **one owner per table**; others use its API/events.
|
|
273
|
+
|
|
274
|
+
```ts
|
|
275
|
+
// ❌ distributed monolith — billing reaches into orders' tables
|
|
276
|
+
const orders = await billingDb.$queryRaw`SELECT * FROM orders WHERE ...`;
|
|
277
|
+
|
|
278
|
+
// ✅ ownership respected — ask the owner (sync) or react to its events (async)
|
|
279
|
+
const orders = await orderServiceClient.listOrders(userId); // sync API
|
|
280
|
+
// or subscribe to OrderPlaced / OrderPaid events and keep a local read model
|
|
281
|
+
```
|
|
282
|
+
- **Modular monolith (T1):** enforce ownership *logically* — separate schemas, interface-only access
|
|
283
|
+
between modules (ArchUnit / Spring Modulith / Packwerk / lint boundaries). Physical single DB is fine.
|
|
284
|
+
- **Split a service only when** a module has a genuine independent deploy/scale/regulatory need — and
|
|
285
|
+
fix data ownership **before** splitting, or you get a distributed monolith (shared DB + coupled
|
|
286
|
+
deploys = worst of both worlds).
|
|
287
|
+
- Cross-service reporting/joins move to a read model fed by events, not cross-service SQL joins.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
## When NOT to reach for these
|
|
292
|
+
|
|
293
|
+
- **CQRS / event sourcing:** only where read and write loads genuinely diverge. Otherwise it's
|
|
294
|
+
accidental complexity — a plain repository is better.
|
|
295
|
+
- **Service mesh / cell-based:** T3 (Kubernetes / real scale) concerns. On a monolith or a couple of
|
|
296
|
+
serverless functions they add operational cost with no payoff. For net-new clusters that *do* need a
|
|
297
|
+
mesh, prefer **ambient/sidecarless** (Istio ambient) over per-pod sidecars.
|
|
298
|
+
- **BFF:** skip when one client, or all clients share a shape.
|
|
@@ -0,0 +1,403 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: backend-realtime
|
|
3
|
+
description: >
|
|
4
|
+
Implement real-time features with WebSockets, Supabase Realtime, or
|
|
5
|
+
Server-Sent Events. Use when "real-time", "live updates", "WebSocket",
|
|
6
|
+
"notifications", "chat", "presence", "collaborative", "live data", or
|
|
7
|
+
"instant sync".
|
|
8
|
+
license: MIT
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Real-time Features Skill
|
|
12
|
+
|
|
13
|
+
**Degree of freedom: MIXED.** Which transport and event `[HIGH freedom]`;
|
|
14
|
+
existing-subscription probes and unmount cleanup `[LOW freedom — run exactly]`.
|
|
15
|
+
|
|
16
|
+
## How to reason
|
|
17
|
+
|
|
18
|
+
1. **Observe** — existing channels, sockets, hooks, cleanup
|
|
19
|
+
2. **Interpret** — new subscription vs reuse vs transport mismatch
|
|
20
|
+
3. **Classify** — postgres-changes / presence / broadcast / SSE / optimistic
|
|
21
|
+
4. **Severity** — leaked or duplicate subscription outranks a missing typing indicator
|
|
22
|
+
|
|
23
|
+
## Worked example
|
|
24
|
+
|
|
25
|
+
> **Observe:** chat mounts `useRealtimeMessages` twice; no untrack on leave; `package.json` already has `@supabase/supabase-js`.
|
|
26
|
+
> **Interpret:** duplicate INSERT listeners; leftover presence.
|
|
27
|
+
> **Classify:** one channel hook with unmount cleanup; reuse Supabase Realtime — do not add Socket.io.
|
|
28
|
+
> **Verify:** one `postgres_changes` subscription; `removeChannel` on unmount; no second socket lib.
|
|
29
|
+
|
|
30
|
+
## Self-critique before reporting
|
|
31
|
+
|
|
32
|
+
- **Existing first** — grepped channels/sockets before adding a library
|
|
33
|
+
- **Cleanup** — every subscribe has unmount `removeChannel` / `close`
|
|
34
|
+
- **One listener** — no duplicate subscription for the same event
|
|
35
|
+
- **Right owner** — FE↔BE payload mismatch → `debug-fe-be-integration`; queue/job instead of live push → `backend-patterns`
|
|
36
|
+
|
|
37
|
+
Implement live, collaborative features using WebSockets, Supabase Realtime, and Server-Sent Events.
|
|
38
|
+
|
|
39
|
+
> Code examples assume a **Next.js App Router + Supabase** stack
|
|
40
|
+
> (`@/lib/supabase/client`, `'use client'`). Adapt import paths and row types to
|
|
41
|
+
> the detected stack.
|
|
42
|
+
|
|
43
|
+
## Check existing first [LOW freedom — run exactly]
|
|
44
|
+
|
|
45
|
+
**Before implementing ANY real-time feature, verify:**
|
|
46
|
+
|
|
47
|
+
1. **Check for existing real-time setup:**
|
|
48
|
+
```bash
|
|
49
|
+
cat package.json | grep -i "socket\|realtime\|pusher\|ably"
|
|
50
|
+
rg "supabase.*channel|useSubscription|WebSocket" --type ts --type tsx
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
2. **Check for existing patterns:**
|
|
54
|
+
```bash
|
|
55
|
+
rg "on\('INSERT'\|on\('UPDATE'\|subscribe\(" --type ts
|
|
56
|
+
ls -la src/hooks/use*Realtime* src/lib/realtime* 2>/dev/null
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
3. **Check Supabase config:**
|
|
60
|
+
```bash
|
|
61
|
+
rg "createClient|supabaseUrl" --type ts -l
|
|
62
|
+
grep -oh "SUPABASE_[A-Z_]*" .env* 2>/dev/null | sort -u # names only — never print values
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Why:** Real-time connections are stateful. Don't create duplicate subscriptions.
|
|
66
|
+
|
|
67
|
+
## Supabase Realtime [HIGH freedom]
|
|
68
|
+
|
|
69
|
+
### Subscribe to Database Changes
|
|
70
|
+
|
|
71
|
+
The canonical `useRealtimeMessages` hook (initial fetch + INSERT/DELETE
|
|
72
|
+
subscription with unmount cleanup) is in
|
|
73
|
+
[`references/patterns.md`](references/patterns.md).
|
|
74
|
+
|
|
75
|
+
### Presence (Online Users)
|
|
76
|
+
```tsx
|
|
77
|
+
'use client'
|
|
78
|
+
import { useEffect, useState } from 'react'
|
|
79
|
+
import { createClient } from '@/lib/supabase/client'
|
|
80
|
+
import type { RealtimePresenceState } from '@supabase/supabase-js'
|
|
81
|
+
|
|
82
|
+
interface UserPresence {
|
|
83
|
+
id: string
|
|
84
|
+
name: string
|
|
85
|
+
avatar: string
|
|
86
|
+
online_at: string
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function usePresence(roomId: string, currentUser: UserPresence) {
|
|
90
|
+
const [onlineUsers, setOnlineUsers] = useState<UserPresence[]>([])
|
|
91
|
+
const supabase = createClient()
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
const channel = supabase.channel(`presence:${roomId}`)
|
|
95
|
+
|
|
96
|
+
channel
|
|
97
|
+
.on('presence', { event: 'sync' }, () => {
|
|
98
|
+
const state = channel.presenceState<UserPresence>()
|
|
99
|
+
const users = Object.values(state).flat()
|
|
100
|
+
setOnlineUsers(users)
|
|
101
|
+
})
|
|
102
|
+
.on('presence', { event: 'join' }, ({ newPresences }) => {
|
|
103
|
+
console.log('User joined:', newPresences)
|
|
104
|
+
})
|
|
105
|
+
.on('presence', { event: 'leave' }, ({ leftPresences }) => {
|
|
106
|
+
console.log('User left:', leftPresences)
|
|
107
|
+
})
|
|
108
|
+
.subscribe(async (status) => {
|
|
109
|
+
if (status === 'SUBSCRIBED') {
|
|
110
|
+
await channel.track(currentUser)
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
channel.untrack()
|
|
116
|
+
supabase.removeChannel(channel)
|
|
117
|
+
}
|
|
118
|
+
}, [roomId, currentUser, supabase])
|
|
119
|
+
|
|
120
|
+
return onlineUsers
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Broadcast (Custom Events)
|
|
125
|
+
```tsx
|
|
126
|
+
'use client'
|
|
127
|
+
import { useEffect, useCallback } from 'react'
|
|
128
|
+
import { createClient } from '@/lib/supabase/client'
|
|
129
|
+
|
|
130
|
+
export function useBroadcast(channelName: string) {
|
|
131
|
+
const supabase = createClient()
|
|
132
|
+
const channelRef = useRef<ReturnType<typeof supabase.channel> | null>(null)
|
|
133
|
+
|
|
134
|
+
useEffect(() => {
|
|
135
|
+
channelRef.current = supabase.channel(channelName)
|
|
136
|
+
|
|
137
|
+
channelRef.current
|
|
138
|
+
.on('broadcast', { event: 'cursor-move' }, ({ payload }) => {
|
|
139
|
+
// Handle cursor position updates from others
|
|
140
|
+
console.log('Cursor moved:', payload)
|
|
141
|
+
})
|
|
142
|
+
.on('broadcast', { event: 'typing' }, ({ payload }) => {
|
|
143
|
+
// Handle typing indicators
|
|
144
|
+
console.log('User typing:', payload)
|
|
145
|
+
})
|
|
146
|
+
.subscribe()
|
|
147
|
+
|
|
148
|
+
return () => {
|
|
149
|
+
if (channelRef.current) {
|
|
150
|
+
supabase.removeChannel(channelRef.current)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}, [channelName, supabase])
|
|
154
|
+
|
|
155
|
+
const broadcast = useCallback((event: string, payload: unknown) => {
|
|
156
|
+
channelRef.current?.send({
|
|
157
|
+
type: 'broadcast',
|
|
158
|
+
event,
|
|
159
|
+
payload,
|
|
160
|
+
})
|
|
161
|
+
}, [])
|
|
162
|
+
|
|
163
|
+
return { broadcast }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Usage: Collaborative cursors
|
|
167
|
+
function CollaborativeCanvas() {
|
|
168
|
+
const { broadcast } = useBroadcast('canvas:123')
|
|
169
|
+
|
|
170
|
+
const handleMouseMove = (e: React.MouseEvent) => {
|
|
171
|
+
broadcast('cursor-move', {
|
|
172
|
+
userId: currentUser.id,
|
|
173
|
+
x: e.clientX,
|
|
174
|
+
y: e.clientY,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## TanStack Query + Real-time [HIGH freedom]
|
|
181
|
+
|
|
182
|
+
```tsx
|
|
183
|
+
import { useQuery, useQueryClient } from '@tanstack/react-query'
|
|
184
|
+
import { useEffect } from 'react'
|
|
185
|
+
|
|
186
|
+
export function useRealtimeQuery<T>(
|
|
187
|
+
queryKey: string[],
|
|
188
|
+
queryFn: () => Promise<T>,
|
|
189
|
+
table: string,
|
|
190
|
+
filter?: string
|
|
191
|
+
) {
|
|
192
|
+
const queryClient = useQueryClient()
|
|
193
|
+
const supabase = createClient()
|
|
194
|
+
|
|
195
|
+
const query = useQuery({
|
|
196
|
+
queryKey,
|
|
197
|
+
queryFn,
|
|
198
|
+
})
|
|
199
|
+
|
|
200
|
+
useEffect(() => {
|
|
201
|
+
const channel = supabase
|
|
202
|
+
.channel(`${table}-changes`)
|
|
203
|
+
.on(
|
|
204
|
+
'postgres_changes',
|
|
205
|
+
{
|
|
206
|
+
event: '*',
|
|
207
|
+
schema: 'public',
|
|
208
|
+
table,
|
|
209
|
+
filter,
|
|
210
|
+
},
|
|
211
|
+
() => {
|
|
212
|
+
// Invalidate and refetch on any change
|
|
213
|
+
queryClient.invalidateQueries({ queryKey })
|
|
214
|
+
}
|
|
215
|
+
)
|
|
216
|
+
.subscribe()
|
|
217
|
+
|
|
218
|
+
return () => {
|
|
219
|
+
supabase.removeChannel(channel)
|
|
220
|
+
}
|
|
221
|
+
}, [table, filter, queryKey, queryClient, supabase])
|
|
222
|
+
|
|
223
|
+
return query
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Usage
|
|
227
|
+
function ProductList() {
|
|
228
|
+
const { data: products } = useRealtimeQuery(
|
|
229
|
+
['products'],
|
|
230
|
+
() => fetchProducts(),
|
|
231
|
+
'products'
|
|
232
|
+
)
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
## Optimistic Updates [HIGH freedom]
|
|
237
|
+
|
|
238
|
+
```tsx
|
|
239
|
+
'use client'
|
|
240
|
+
import { useOptimistic, useTransition } from 'react'
|
|
241
|
+
import { addMessage } from '@/app/actions'
|
|
242
|
+
|
|
243
|
+
function Chat({ initialMessages }: { initialMessages: Message[] }) {
|
|
244
|
+
const [isPending, startTransition] = useTransition()
|
|
245
|
+
const [optimisticMessages, addOptimisticMessage] = useOptimistic(
|
|
246
|
+
initialMessages,
|
|
247
|
+
(state, newMessage: Message) => [...state, newMessage]
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
async function handleSubmit(formData: FormData) {
|
|
251
|
+
const content = formData.get('content') as string
|
|
252
|
+
|
|
253
|
+
// Optimistic update - instant UI feedback
|
|
254
|
+
const optimisticMessage: Message = {
|
|
255
|
+
id: crypto.randomUUID(),
|
|
256
|
+
content,
|
|
257
|
+
created_at: new Date().toISOString(),
|
|
258
|
+
user_id: currentUser.id,
|
|
259
|
+
pending: true,
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
startTransition(async () => {
|
|
263
|
+
addOptimisticMessage(optimisticMessage)
|
|
264
|
+
await addMessage(content) // Server Action
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return (
|
|
269
|
+
<div>
|
|
270
|
+
{optimisticMessages.map((msg) => (
|
|
271
|
+
<div key={msg.id} className={msg.pending ? 'opacity-50' : ''}>
|
|
272
|
+
{msg.content}
|
|
273
|
+
</div>
|
|
274
|
+
))}
|
|
275
|
+
<form action={handleSubmit}>
|
|
276
|
+
<input name="content" />
|
|
277
|
+
<button type="submit" disabled={isPending}>Send</button>
|
|
278
|
+
</form>
|
|
279
|
+
</div>
|
|
280
|
+
)
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## Server-Sent Events (SSE) [HIGH freedom]
|
|
285
|
+
|
|
286
|
+
```tsx
|
|
287
|
+
// app/api/events/route.ts
|
|
288
|
+
export async function GET(request: Request) {
|
|
289
|
+
const encoder = new TextEncoder()
|
|
290
|
+
|
|
291
|
+
const stream = new ReadableStream({
|
|
292
|
+
async start(controller) {
|
|
293
|
+
const send = (data: unknown) => {
|
|
294
|
+
controller.enqueue(
|
|
295
|
+
encoder.encode(`data: ${JSON.stringify(data)}\n\n`)
|
|
296
|
+
)
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Send initial data
|
|
300
|
+
send({ type: 'connected', timestamp: Date.now() })
|
|
301
|
+
|
|
302
|
+
// Subscribe to changes (e.g., from database)
|
|
303
|
+
const interval = setInterval(() => {
|
|
304
|
+
send({ type: 'heartbeat', timestamp: Date.now() })
|
|
305
|
+
}, 30000)
|
|
306
|
+
|
|
307
|
+
// Cleanup when the client disconnects
|
|
308
|
+
request.signal.addEventListener('abort', () => {
|
|
309
|
+
clearInterval(interval)
|
|
310
|
+
controller.close()
|
|
311
|
+
})
|
|
312
|
+
},
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
return new Response(stream, {
|
|
316
|
+
headers: {
|
|
317
|
+
'Content-Type': 'text/event-stream',
|
|
318
|
+
'Cache-Control': 'no-cache',
|
|
319
|
+
'Connection': 'keep-alive',
|
|
320
|
+
},
|
|
321
|
+
})
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Client hook
|
|
325
|
+
function useSSE(url: string) {
|
|
326
|
+
const [data, setData] = useState(null)
|
|
327
|
+
|
|
328
|
+
useEffect(() => {
|
|
329
|
+
const eventSource = new EventSource(url)
|
|
330
|
+
|
|
331
|
+
eventSource.onmessage = (event) => {
|
|
332
|
+
setData(JSON.parse(event.data))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
eventSource.onerror = () => {
|
|
336
|
+
eventSource.close()
|
|
337
|
+
// Implement reconnection logic
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return () => eventSource.close()
|
|
341
|
+
}, [url])
|
|
342
|
+
|
|
343
|
+
return data
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## Typing Indicators [HIGH freedom]
|
|
348
|
+
|
|
349
|
+
```tsx
|
|
350
|
+
function useTypingIndicator(channelName: string, userId: string) {
|
|
351
|
+
const [typingUsers, setTypingUsers] = useState<string[]>([])
|
|
352
|
+
const supabase = createClient()
|
|
353
|
+
const timeoutRef = useRef<NodeJS.Timeout>()
|
|
354
|
+
|
|
355
|
+
useEffect(() => {
|
|
356
|
+
const channel = supabase.channel(channelName)
|
|
357
|
+
|
|
358
|
+
channel
|
|
359
|
+
.on('broadcast', { event: 'typing' }, ({ payload }) => {
|
|
360
|
+
if (payload.userId !== userId) {
|
|
361
|
+
setTypingUsers((prev) =>
|
|
362
|
+
prev.includes(payload.userId) ? prev : [...prev, payload.userId]
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
// Remove after 3 seconds of no activity
|
|
366
|
+
setTimeout(() => {
|
|
367
|
+
setTypingUsers((prev) =>
|
|
368
|
+
prev.filter((id) => id !== payload.userId)
|
|
369
|
+
)
|
|
370
|
+
}, 3000)
|
|
371
|
+
}
|
|
372
|
+
})
|
|
373
|
+
.subscribe()
|
|
374
|
+
|
|
375
|
+
return () => {
|
|
376
|
+
supabase.removeChannel(channel)
|
|
377
|
+
}
|
|
378
|
+
}, [channelName, userId, supabase])
|
|
379
|
+
|
|
380
|
+
const sendTyping = useCallback(() => {
|
|
381
|
+
clearTimeout(timeoutRef.current)
|
|
382
|
+
|
|
383
|
+
supabase.channel(channelName).send({
|
|
384
|
+
type: 'broadcast',
|
|
385
|
+
event: 'typing',
|
|
386
|
+
payload: { userId },
|
|
387
|
+
})
|
|
388
|
+
}, [channelName, userId, supabase])
|
|
389
|
+
|
|
390
|
+
return { typingUsers, sendTyping }
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
## Validation [LOW freedom — do not skip]
|
|
395
|
+
|
|
396
|
+
After implementing real-time features:
|
|
397
|
+
|
|
398
|
+
1. **Connection handling** → Reconnects on disconnect, shows status
|
|
399
|
+
2. **Error handling** → Graceful degradation when real-time unavailable
|
|
400
|
+
3. **Memory leaks** → All subscriptions cleaned up on unmount
|
|
401
|
+
4. **Duplicate subscriptions** → No multiple listeners for same event
|
|
402
|
+
5. **Optimistic updates** → UI feels instant, handles conflicts
|
|
403
|
+
6. **Mobile** → Works on spotty connections, battery efficient
|