@skill-graph/cli 0.5.6

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.
Files changed (330) hide show
  1. package/CHANGELOG.md +247 -0
  2. package/LICENSE +200 -0
  3. package/NOTICE +62 -0
  4. package/README.md +398 -0
  5. package/SKILL_GRAPH.md +443 -0
  6. package/bin/skill-graph.js +374 -0
  7. package/docs/ADOPTION.md +117 -0
  8. package/docs/CONFORMANCE.md +66 -0
  9. package/docs/PRIMER.md +384 -0
  10. package/docs/QUICKSTART-30MIN.md +333 -0
  11. package/docs/ROUTING-METRICS.md +120 -0
  12. package/docs/SKILL-MD-FORMAT-COMPATIBILITY.md +127 -0
  13. package/docs/SKILL_AUDIT_CHECKLIST.md +199 -0
  14. package/docs/SKILL_AUDIT_LOOP.md +195 -0
  15. package/docs/SKILL_METADATA_PROTOCOL.md +609 -0
  16. package/docs/_archived/marketplace-publication-priority-2026-05-18.md +239 -0
  17. package/docs/adr/0001-predicate-set.md +69 -0
  18. package/docs/adr/0002-json-ld-context.md +82 -0
  19. package/docs/adr/0003-ontoclean-rigidity-tags.md +65 -0
  20. package/docs/adr/0004-persistent-identifiers.md +74 -0
  21. package/docs/adr/0005-freshness-consolidation.md +70 -0
  22. package/docs/adr/0006-revise-predicate-rename.md +105 -0
  23. package/docs/adr/0007-audit-loop-cadence.md +99 -0
  24. package/docs/adr/0008-skill-surface-split-and-curation-policy.md +93 -0
  25. package/docs/category-consumers.md +168 -0
  26. package/docs/concept-map.md +194 -0
  27. package/docs/diagrams/drift-states.mmd +21 -0
  28. package/docs/diagrams/manifest-pipeline.mmd +25 -0
  29. package/docs/diagrams/routing-harness.mmd +41 -0
  30. package/docs/diagrams/starter-graph.mmd +53 -0
  31. package/docs/field-decision-guide.md +315 -0
  32. package/docs/field-rationale.md +211 -0
  33. package/docs/field-reference.generated.md +624 -0
  34. package/docs/field-reference.md +1426 -0
  35. package/docs/glossary.md +190 -0
  36. package/docs/head-noun-glossary.md +63 -0
  37. package/docs/images/audit-phases.png +0 -0
  38. package/docs/images/drift-states.png +0 -0
  39. package/docs/images/graded-mode.png +0 -0
  40. package/docs/images/manifest-pipeline.png +0 -0
  41. package/docs/images/routing-harness.png +0 -0
  42. package/docs/images/skill-anatomy.png +0 -0
  43. package/docs/images/starter-graph.png +0 -0
  44. package/docs/images/system-model.png +0 -0
  45. package/docs/integrations/github-actions.md +155 -0
  46. package/docs/manifest-field-mapping.md +443 -0
  47. package/docs/marketplace-publication-queue.generated.md +240 -0
  48. package/docs/marketplace-release-agent-prompt.md +82 -0
  49. package/docs/marketplace-skill-candidate-list.md +272 -0
  50. package/docs/marketplace-syndication.md +222 -0
  51. package/docs/migration-sample-review.md +155 -0
  52. package/docs/migrations/v4-to-v5.md +168 -0
  53. package/docs/migrations/v5-to-v6.md +221 -0
  54. package/docs/name-exceptions.yaml +37 -0
  55. package/docs/plans/marketplace-p1-public-migration-plan.md +41 -0
  56. package/docs/plans/multi-root-workspace.md +148 -0
  57. package/docs/plans/scripts-roadmap.md +107 -0
  58. package/docs/plans/v4-schema-bump.md +160 -0
  59. package/docs/plans/wave-2-extraction.md +122 -0
  60. package/docs/positioning-vs-marketplaces.md +175 -0
  61. package/docs/proposals/skill-audit-loop-positioning.md +160 -0
  62. package/docs/quality-doctrine.md +138 -0
  63. package/docs/recommended-skills.md +150 -0
  64. package/docs/research/skill-comprehension-eval-research.md +1830 -0
  65. package/docs/research/skill-retrieval-evidence.md +66 -0
  66. package/docs/skill-metadata-protocol.md +471 -0
  67. package/docs/skills-sh-maintainer-cleanup-request.md +80 -0
  68. package/examples/audits/a11y/findings.md +52 -0
  69. package/examples/audits/a11y/scorecard.md +21 -0
  70. package/examples/audits/a11y/verdict.md +44 -0
  71. package/examples/audits/debugging/findings.md +59 -0
  72. package/examples/audits/debugging/scorecard.md +22 -0
  73. package/examples/audits/debugging/verdict.md +33 -0
  74. package/examples/audits/documentation/findings.md +59 -0
  75. package/examples/audits/documentation/scorecard.md +22 -0
  76. package/examples/audits/documentation/verdict.md +33 -0
  77. package/examples/evals/a11y.json +140 -0
  78. package/examples/evals/api-design.json +52 -0
  79. package/examples/evals/code-review.json +52 -0
  80. package/examples/evals/data-modeling.json +52 -0
  81. package/examples/evals/database-migration.json +52 -0
  82. package/examples/evals/debugging.json +118 -0
  83. package/examples/evals/dependency-architecture.json +52 -0
  84. package/examples/evals/design-system-architecture.json +52 -0
  85. package/examples/evals/error-tracking.json +52 -0
  86. package/examples/evals/event-contract-design.json +52 -0
  87. package/examples/evals/form-ux-architecture.json +52 -0
  88. package/examples/evals/framework-fit-analysis.json +52 -0
  89. package/examples/evals/graph-audit.json +139 -0
  90. package/examples/evals/information-architecture.json +52 -0
  91. package/examples/evals/interaction-feedback.json +52 -0
  92. package/examples/evals/interaction-patterns.json +52 -0
  93. package/examples/evals/layout-composition.json +52 -0
  94. package/examples/evals/lint-overlay.json +117 -0
  95. package/examples/evals/microcopy.json +52 -0
  96. package/examples/evals/observability-modeling.json +52 -0
  97. package/examples/evals/pattern-recognition.json +96 -0
  98. package/examples/evals/performance-engineering.json +52 -0
  99. package/examples/evals/refactor.json +128 -0
  100. package/examples/evals/semiotics.json +52 -0
  101. package/examples/evals/skill-infrastructure.json +96 -0
  102. package/examples/evals/skill-router.json +140 -0
  103. package/examples/evals/skill-router.routing.json +113 -0
  104. package/examples/evals/system-interface-contracts.json +52 -0
  105. package/examples/evals/task-analysis.json +52 -0
  106. package/examples/evals/testing-strategy.json +118 -0
  107. package/examples/evals/type-safety.json +249 -0
  108. package/examples/evals/visual-design-foundations.json +52 -0
  109. package/examples/evals/webhook-integration.json +52 -0
  110. package/examples/exports/a11y.skill-md.md +80 -0
  111. package/examples/exports/debugging.skill-md.md +80 -0
  112. package/examples/exports/refactor.skill-md.md +78 -0
  113. package/examples/exports/testing-strategy.skill-md.md +81 -0
  114. package/examples/projects/markdown-static-site/README.md +115 -0
  115. package/examples/projects/markdown-static-site/skills/content-source-router/SKILL.md +131 -0
  116. package/examples/projects/markdown-static-site/skills/image-optimization-pipeline-config/SKILL.md +132 -0
  117. package/examples/projects/markdown-static-site/skills/link-rot-detection/SKILL.md +103 -0
  118. package/examples/projects/markdown-static-site/skills/markdown-post-frontmatter-validation/SKILL.md +133 -0
  119. package/examples/projects/markdown-static-site/skills/migrate-posts-to-v2-frontmatter/SKILL.md +140 -0
  120. package/examples/projects/saas-stripe-postgres/README.md +208 -0
  121. package/examples/projects/saas-stripe-postgres/db/migrations/0004_canonicalize_orders.sql +37 -0
  122. package/examples/projects/saas-stripe-postgres/db/schema.sql +112 -0
  123. package/examples/projects/saas-stripe-postgres/skills/migrate-orders-to-canonical-schema/SKILL.md +149 -0
  124. package/examples/projects/saas-stripe-postgres/skills/nextjs-server-action-validation/SKILL.md +154 -0
  125. package/examples/projects/saas-stripe-postgres/skills/payment-provider-router/SKILL.md +153 -0
  126. package/examples/projects/saas-stripe-postgres/skills/postgres-rls-pattern/SKILL.md +163 -0
  127. package/examples/projects/saas-stripe-postgres/skills/stripe-webhook-signature-verification/SKILL.md +137 -0
  128. package/examples/protocol/skill-metadata-template.md +301 -0
  129. package/examples/protocol/skills.manifest.sample.json +13245 -0
  130. package/examples/skill-metadata-template.md +317 -0
  131. package/examples/skills.manifest.sample.json +13519 -0
  132. package/examples/tests/v3-1-skos-fixture/SKILL.md +93 -0
  133. package/marketplace/README.md +17 -0
  134. package/marketplace/skills/a11y/SKILL.md +66 -0
  135. package/marketplace/skills/acid-fundamentals/SKILL.md +106 -0
  136. package/marketplace/skills/agent-engineering/SKILL.md +386 -0
  137. package/marketplace/skills/agent-eval-design/SKILL.md +55 -0
  138. package/marketplace/skills/ai-native-development/SKILL.md +294 -0
  139. package/marketplace/skills/api-design/SKILL.md +60 -0
  140. package/marketplace/skills/architecture-decision-records/SKILL.md +55 -0
  141. package/marketplace/skills/background-jobs/SKILL.md +265 -0
  142. package/marketplace/skills/bounded-context-mapping/SKILL.md +55 -0
  143. package/marketplace/skills/cap-theorem-tradeoffs/SKILL.md +127 -0
  144. package/marketplace/skills/client-server-boundary/SKILL.md +187 -0
  145. package/marketplace/skills/code-review/SKILL.md +120 -0
  146. package/marketplace/skills/color-system-design/SKILL.md +43 -0
  147. package/marketplace/skills/component-architecture/SKILL.md +126 -0
  148. package/marketplace/skills/compression/SKILL.md +112 -0
  149. package/marketplace/skills/conceptual-modeling/SKILL.md +181 -0
  150. package/marketplace/skills/connection-pooling/SKILL.md +105 -0
  151. package/marketplace/skills/constraint-awareness/SKILL.md +287 -0
  152. package/marketplace/skills/content-monitor/SKILL.md +209 -0
  153. package/marketplace/skills/context-engineering/SKILL.md +320 -0
  154. package/marketplace/skills/context-graph/SKILL.md +174 -0
  155. package/marketplace/skills/context-management/SKILL.md +174 -0
  156. package/marketplace/skills/context-window/SKILL.md +239 -0
  157. package/marketplace/skills/contract-testing/SKILL.md +120 -0
  158. package/marketplace/skills/cron-scheduling/SKILL.md +223 -0
  159. package/marketplace/skills/dark-mode-implementation/SKILL.md +47 -0
  160. package/marketplace/skills/data-modeling/SKILL.md +59 -0
  161. package/marketplace/skills/data-modeling-fundamentals/SKILL.md +117 -0
  162. package/marketplace/skills/database-migration/SKILL.md +429 -0
  163. package/marketplace/skills/debugging/SKILL.md +67 -0
  164. package/marketplace/skills/dependency-architecture/SKILL.md +58 -0
  165. package/marketplace/skills/design-module-composition/SKILL.md +43 -0
  166. package/marketplace/skills/design-system-architecture/SKILL.md +61 -0
  167. package/marketplace/skills/design-thinking/SKILL.md +44 -0
  168. package/marketplace/skills/diagnosis/SKILL.md +296 -0
  169. package/marketplace/skills/diff-analysis/SKILL.md +188 -0
  170. package/marketplace/skills/e2e-test-design/SKILL.md +113 -0
  171. package/marketplace/skills/entity-relationship-modeling/SKILL.md +218 -0
  172. package/marketplace/skills/epistemic-grounding/SKILL.md +112 -0
  173. package/marketplace/skills/error-boundary/SKILL.md +235 -0
  174. package/marketplace/skills/error-tracking/SKILL.md +261 -0
  175. package/marketplace/skills/eval-driven-development/SKILL.md +147 -0
  176. package/marketplace/skills/evaluation/SKILL.md +113 -0
  177. package/marketplace/skills/event-contract-design/SKILL.md +60 -0
  178. package/marketplace/skills/event-storming/SKILL.md +56 -0
  179. package/marketplace/skills/form-ux-architecture/SKILL.md +60 -0
  180. package/marketplace/skills/framework-fit-analysis/SKILL.md +59 -0
  181. package/marketplace/skills/frontend-architecture/SKILL.md +43 -0
  182. package/marketplace/skills/generative-ui/SKILL.md +118 -0
  183. package/marketplace/skills/graph-audit/SKILL.md +81 -0
  184. package/marketplace/skills/guardrails/SKILL.md +118 -0
  185. package/marketplace/skills/hooks-patterns/SKILL.md +185 -0
  186. package/marketplace/skills/http-semantics/SKILL.md +136 -0
  187. package/marketplace/skills/ideation/SKILL.md +41 -0
  188. package/marketplace/skills/indexing-strategy/SKILL.md +108 -0
  189. package/marketplace/skills/information-architecture/SKILL.md +59 -0
  190. package/marketplace/skills/integration-test-design/SKILL.md +111 -0
  191. package/marketplace/skills/intent-recognition/SKILL.md +136 -0
  192. package/marketplace/skills/interaction-feedback/SKILL.md +59 -0
  193. package/marketplace/skills/interaction-patterns/SKILL.md +59 -0
  194. package/marketplace/skills/journey-mapping/SKILL.md +41 -0
  195. package/marketplace/skills/keywords/SKILL.md +213 -0
  196. package/marketplace/skills/knowledge-modeling/SKILL.md +232 -0
  197. package/marketplace/skills/layout-composition/SKILL.md +59 -0
  198. package/marketplace/skills/linguistics/SKILL.md +429 -0
  199. package/marketplace/skills/lint-overlay/SKILL.md +76 -0
  200. package/marketplace/skills/mental-models/SKILL.md +126 -0
  201. package/marketplace/skills/merge-queue/SKILL.md +94 -0
  202. package/marketplace/skills/methodology/SKILL.md +317 -0
  203. package/marketplace/skills/microcopy/SKILL.md +232 -0
  204. package/marketplace/skills/middleware-patterns/SKILL.md +363 -0
  205. package/marketplace/skills/mobile-responsive-ux/SKILL.md +287 -0
  206. package/marketplace/skills/mutation-testing/SKILL.md +112 -0
  207. package/marketplace/skills/naming-conventions/SKILL.md +112 -0
  208. package/marketplace/skills/observability-modeling/SKILL.md +59 -0
  209. package/marketplace/skills/ontology-modeling/SKILL.md +67 -0
  210. package/marketplace/skills/owasp-security/SKILL.md +153 -0
  211. package/marketplace/skills/pattern-recognition/SKILL.md +472 -0
  212. package/marketplace/skills/performance-budgets/SKILL.md +185 -0
  213. package/marketplace/skills/performance-engineering/SKILL.md +58 -0
  214. package/marketplace/skills/performance-testing/SKILL.md +125 -0
  215. package/marketplace/skills/printify/SKILL.md +42 -0
  216. package/marketplace/skills/prioritization/SKILL.md +118 -0
  217. package/marketplace/skills/problem-framing/SKILL.md +41 -0
  218. package/marketplace/skills/problem-locating-solving/SKILL.md +203 -0
  219. package/marketplace/skills/project-knowledge-extraction/SKILL.md +54 -0
  220. package/marketplace/skills/prompt-craft/SKILL.md +134 -0
  221. package/marketplace/skills/prompt-injection-defense/SKILL.md +132 -0
  222. package/marketplace/skills/property-based-testing/SKILL.md +100 -0
  223. package/marketplace/skills/prototyping/SKILL.md +43 -0
  224. package/marketplace/skills/query-optimization/SKILL.md +144 -0
  225. package/marketplace/skills/real-time-updates/SKILL.md +324 -0
  226. package/marketplace/skills/ref-patterns/SKILL.md +284 -0
  227. package/marketplace/skills/refactor/SKILL.md +65 -0
  228. package/marketplace/skills/rendering-models/SKILL.md +142 -0
  229. package/marketplace/skills/replication-patterns/SKILL.md +110 -0
  230. package/marketplace/skills/research-synthesis/SKILL.md +41 -0
  231. package/marketplace/skills/route-handler-design/SKILL.md +347 -0
  232. package/marketplace/skills/schema-evolution/SKILL.md +140 -0
  233. package/marketplace/skills/security-fundamentals/SKILL.md +139 -0
  234. package/marketplace/skills/semantic-center/SKILL.md +194 -0
  235. package/marketplace/skills/semantic-relations/SKILL.md +250 -0
  236. package/marketplace/skills/semantics/SKILL.md +366 -0
  237. package/marketplace/skills/semiotics/SKILL.md +230 -0
  238. package/marketplace/skills/seo-strategy/SKILL.md +260 -0
  239. package/marketplace/skills/server-actions-design/SKILL.md +243 -0
  240. package/marketplace/skills/server-components-design/SKILL.md +190 -0
  241. package/marketplace/skills/sharding-strategy/SKILL.md +123 -0
  242. package/marketplace/skills/shopify/SKILL.md +42 -0
  243. package/marketplace/skills/skill-infrastructure/SKILL.md +320 -0
  244. package/marketplace/skills/skill-router/SKILL.md +71 -0
  245. package/marketplace/skills/skill-scaffold/SKILL.md +105 -0
  246. package/marketplace/skills/snapshot-testing/SKILL.md +120 -0
  247. package/marketplace/skills/spec-driven-development/SKILL.md +148 -0
  248. package/marketplace/skills/state-machine-modeling/SKILL.md +56 -0
  249. package/marketplace/skills/state-management/SKILL.md +134 -0
  250. package/marketplace/skills/streaming-architecture/SKILL.md +194 -0
  251. package/marketplace/skills/summarization/SKILL.md +156 -0
  252. package/marketplace/skills/suspense-patterns/SKILL.md +265 -0
  253. package/marketplace/skills/system-interface-contracts/SKILL.md +59 -0
  254. package/marketplace/skills/task-analysis/SKILL.md +201 -0
  255. package/marketplace/skills/taxonomy-design/SKILL.md +66 -0
  256. package/marketplace/skills/test-coverage-strategy/SKILL.md +108 -0
  257. package/marketplace/skills/test-doubles-design/SKILL.md +98 -0
  258. package/marketplace/skills/test-driven-development/SKILL.md +96 -0
  259. package/marketplace/skills/testing-strategy/SKILL.md +67 -0
  260. package/marketplace/skills/theme-system-design/SKILL.md +43 -0
  261. package/marketplace/skills/tool-call-flow/SKILL.md +229 -0
  262. package/marketplace/skills/tool-call-strategy/SKILL.md +292 -0
  263. package/marketplace/skills/transaction-isolation/SKILL.md +98 -0
  264. package/marketplace/skills/type-safety/SKILL.md +177 -0
  265. package/marketplace/skills/typography-system/SKILL.md +43 -0
  266. package/marketplace/skills/usability-testing/SKILL.md +43 -0
  267. package/marketplace/skills/user-research/SKILL.md +43 -0
  268. package/marketplace/skills/vercel-composition-patterns/SKILL.md +157 -0
  269. package/marketplace/skills/version-control/SKILL.md +233 -0
  270. package/marketplace/skills/visual-design-foundations/SKILL.md +59 -0
  271. package/marketplace/skills/visual-hierarchy/SKILL.md +43 -0
  272. package/marketplace/skills/webhook-integration/SKILL.md +331 -0
  273. package/marketplace/skills/writing-humanizer/SKILL.md +380 -0
  274. package/package.json +67 -0
  275. package/schemas/manifest.schema.json +811 -0
  276. package/schemas/manifest.v2.schema.json +164 -0
  277. package/schemas/manifest.v3.schema.json +758 -0
  278. package/schemas/manifest.v4.schema.json +755 -0
  279. package/schemas/manifest.v5.schema.json +755 -0
  280. package/schemas/manifest.v6.schema.json +811 -0
  281. package/schemas/skill.context.jsonld +279 -0
  282. package/schemas/skill.schema.json +919 -0
  283. package/schemas/skill.v2.schema.json +201 -0
  284. package/schemas/skill.v3.schema.json +827 -0
  285. package/schemas/skill.v4.schema.json +822 -0
  286. package/schemas/skill.v5.schema.json +830 -0
  287. package/schemas/skill.v6.schema.json +946 -0
  288. package/schemas/vocabulary/keywords.json +180 -0
  289. package/schemas/vocabulary/workspace_tags.json +23 -0
  290. package/scripts/__tests__/migrate-skill-v2-to-v3.test.js +161 -0
  291. package/scripts/__tests__/migrate-skill-v3-to-v4.test.js +158 -0
  292. package/scripts/__tests__/test-export-parser-drift.js +149 -0
  293. package/scripts/__tests__/test-marketplace-export.js +114 -0
  294. package/scripts/__tests__/test-router-paths.js +82 -0
  295. package/scripts/__tests__/test-stability-promotion.js +244 -0
  296. package/scripts/__tests__/test-v3-1-alias-contract.js +109 -0
  297. package/scripts/__tests__/test-v3-1-skos-runtime.js +116 -0
  298. package/scripts/backfill-schema-version.js +198 -0
  299. package/scripts/build-field-reference.js +160 -0
  300. package/scripts/build-retrieval-baseline.js +511 -0
  301. package/scripts/check-markdown-links.js +211 -0
  302. package/scripts/check-protocol-consistency.js +979 -0
  303. package/scripts/export-marketplace-skills.js +610 -0
  304. package/scripts/export-skill.js +374 -0
  305. package/scripts/generate-manifest.js +787 -0
  306. package/scripts/lib/alias-contract.js +83 -0
  307. package/scripts/lib/audit-prompt-builder.js +771 -0
  308. package/scripts/lib/mock-grader.js +134 -0
  309. package/scripts/lib/parse-frontmatter.js +429 -0
  310. package/scripts/lib/roots.js +119 -0
  311. package/scripts/lint/check-archetype-sections.js +185 -0
  312. package/scripts/lint/check-category-enum.js +83 -0
  313. package/scripts/lint/check-routing-eval.js +146 -0
  314. package/scripts/lint/check-routing-quality.js +211 -0
  315. package/scripts/lint/check-stability-promotion.js +220 -0
  316. package/scripts/lint/format-code-frame.js +206 -0
  317. package/scripts/marketplace-install.js +125 -0
  318. package/scripts/migrate-category-to-enum.js +169 -0
  319. package/scripts/migrate-skill-v2-to-v3.js +424 -0
  320. package/scripts/migrate-skill-v3-to-v4.js +200 -0
  321. package/scripts/migrate-skill-v5-to-v6.js +304 -0
  322. package/scripts/restructure-by-category.js +85 -0
  323. package/scripts/seed-publication-classification.js +282 -0
  324. package/scripts/skill-audit.js +893 -0
  325. package/scripts/skill-graph-drift.js +483 -0
  326. package/scripts/skill-graph-route.js +766 -0
  327. package/scripts/skill-graph-routing-eval.js +393 -0
  328. package/scripts/skill-lint.js +1317 -0
  329. package/scripts/skill-overlap.js +213 -0
  330. package/scripts/verify-skill-md-export.js +201 -0
@@ -0,0 +1,324 @@
1
+ ---
2
+ name: real-time-updates
3
+ description: "This skill provides live data update patterns for web applications: webhook-to-UI push patterns, Server-Sent Events for dashboard updates, polling fallback strategies, optimistic updates, and stale data indicators. Load when implementing live dashboards, push notifications to the browser, real-time data refresh, or deciding between SSE, WebSocket, and polling."
4
+ license: MIT
5
+ compatibility: "Markdown, Git, agent-skill runtimes"
6
+ allowed-tools: Read Grep Bash
7
+ metadata:
8
+ metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"engineering\",\"domain\":\"engineering/realtime\",\"scope\":\"portable\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-03-29\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-03-29\\\\\\\"}\",\"eval_artifacts\":\"planned\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"real-time-updates\\\\\\\",\\\\\\\"real\\\\\\\",\\\\\\\"time\\\\\\\",\\\\\\\"updates\\\\\\\"]\",\"triggers\":\"[\\\\\\\"real-time-updates-skill\\\\\\\",\\\\\\\"live-data-skill\\\\\\\",\\\\\\\"push-updates-skill\\\\\\\",\\\\\\\"dashboard-refresh-skill\\\\\\\",\\\\\\\"stale-data-skill\\\\\\\"]\",\"relations\":\"{\\\\\\\"boundary\\\\\\\":[\\\\\\\"background-jobs\\\\\\\",\\\\\\\"cron-scheduling\\\\\\\"],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"interaction-feedback\\\\\\\"]}\",\"portability\":\"{\\\\\\\"readiness\\\\\\\":\\\\\\\"scripted\\\\\\\",\\\\\\\"targets\\\\\\\":[\\\\\\\"skill-md\\\\\\\"]}\",\"lifecycle\":\"{\\\\\\\"stale_after_days\\\\\\\":90,\\\\\\\"review_cadence\\\\\\\":\\\\\\\"quarterly\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/real-time-updates/SKILL.md\"}"
9
+ skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
10
+ skill_graph_protocol: Skill Metadata Protocol v4
11
+ skill_graph_project: Skill Graph
12
+ skill_graph_canonical_skill: skills/real-time-updates/SKILL.md
13
+ ---
14
+ # Real-Time Updates Skill
15
+
16
+ ## Domain Context
17
+
18
+ **What is this skill?** This skill provides live data update patterns for web applications: webhook-to-UI push patterns, Server-Sent Events for dashboard updates, polling fallback strategies, optimistic updates, and stale data indicators. Load when implementing live dashboards, push notifications to the browser, real-time data refresh, or deciding between SSE, WebSocket, and polling.
19
+ > Stale data without a staleness indicator is lying to the user. Every data display must communicate its freshness.
20
+
21
+ ## Coverage
22
+
23
+ This skill covers the decision framework for choosing between Server-Sent Events, WebSocket, and polling, webhook-to-UI push architecture (external event to browser update), optimistic update patterns (instant UI feedback before server confirmation), stale data detection and indicator design, reconnection and recovery strategies for persistent connections, polling fallback when persistent connections fail, and data freshness communication to the user (timestamps, staleness badges, auto-refresh).
24
+
25
+ ## Philosophy
26
+
27
+ Most SaaS dashboards display data that was fetched once and never updated. The user sees a margin figure from 3 minutes ago and believes it is current. When a new order arrives, the dashboard does not update until the user manually refreshes. This creates a false sense of currency that leads to bad decisions. The skill exists because agents build dashboards that fetch data on page load and never update, or overengineer with WebSocket when SSE or smart polling would suffice. The right approach depends on the data characteristics: how often it changes, how critical freshness is, and how many concurrent connections the server can handle.
28
+
29
+ ## Architecture
30
+
31
+ ### Transport Decision Matrix
32
+
33
+ | Criteria | SSE | WebSocket | Polling |
34
+ |----------|-----|-----------|---------|
35
+ | **Direction** | Server -> Client only | Bidirectional | Client -> Server -> Client |
36
+ | **Connection** | Persistent HTTP | Persistent TCP | Repeated HTTP requests |
37
+ | **Browser support** | All modern browsers | All modern browsers | Universal |
38
+ | **Through proxies/CDN** | Good (HTTP/2) | Can be problematic | Perfect |
39
+ | **Reconnection** | Built-in (`EventSource` auto-reconnects) | Manual implementation | N/A (each request independent) |
40
+ | **Serverless friendly** | Partial (connection duration limits) | No (requires persistent server) | Yes |
41
+ | **Best for** | Dashboard updates, notifications | Chat, collaborative editing | Simple status checks, serverless |
42
+
43
+ **Decision flow:**
44
+
45
+ 1. Data flows server-to-client only? --> **SSE**
46
+ 2. Need bidirectional communication? --> **WebSocket** (see `websocket` skill)
47
+ 3. Running on serverless with no persistent connections? --> **Polling**
48
+ 4. Need maximum compatibility with zero infrastructure? --> **Polling**
49
+
50
+ ### Webhook-to-UI Push Architecture
51
+
52
+ External platforms (Shopify, Stripe) send webhooks to the server. The server must relay relevant updates to the browser. This is a three-hop architecture:
53
+
54
+ ```
55
+ External Platform (Shopify)
56
+ |
57
+ | Webhook POST
58
+ v
59
+ Server (API route handler)
60
+ |
61
+ | Process + Store
62
+ v
63
+ Database
64
+ |
65
+ | Notify connected clients
66
+ v
67
+ SSE/WebSocket/Polling endpoint
68
+ |
69
+ | Push update
70
+ v
71
+ Browser (React state update)
72
+ ```
73
+
74
+ **Implementation options for server-to-browser notification:**
75
+
76
+ | Option | How It Works | Latency | Serverless? |
77
+ |--------|-------------|---------|-------------|
78
+ | **Database polling** | Client polls a "last_updated" timestamp | 2-30s | Yes |
79
+ | **SSE from webhook handler** | Webhook handler writes to SSE channel | < 1s | Partial |
80
+ | **Pub/Sub (Redis)** | Webhook publishes, SSE subscriber pushes | < 1s | No (needs Redis) |
81
+ | **Inngest event -> SSE** | Webhook -> Inngest event -> SSE handler | 1-3s | Yes |
82
+
83
+ ## Implementation Patterns
84
+
85
+ ### 1. Server-Sent Events (SSE)
86
+
87
+ ```typescript
88
+ // API route: GET /api/events/dashboard
89
+ export async function GET(request: Request) {
90
+ const encoder = new TextEncoder();
91
+ const stream = new ReadableStream({
92
+ start(controller) {
93
+ const interval = setInterval(async () => {
94
+ const updates = await getRecentUpdates(orgId);
95
+ if (updates.length > 0) {
96
+ const data = `data: ${JSON.stringify(updates)}\n\n`;
97
+ controller.enqueue(encoder.encode(data));
98
+ }
99
+ }, 2000);
100
+
101
+ request.signal.addEventListener('abort', () => {
102
+ clearInterval(interval);
103
+ controller.close();
104
+ });
105
+ },
106
+ });
107
+
108
+ return new Response(stream, {
109
+ headers: {
110
+ 'Content-Type': 'text/event-stream',
111
+ 'Cache-Control': 'no-cache',
112
+ 'Connection': 'keep-alive',
113
+ },
114
+ });
115
+ }
116
+ ```
117
+
118
+ **Client-side SSE consumption:**
119
+
120
+ ```typescript
121
+ // Example pattern only: this repo does not currently ship a dedicated
122
+ // useDashboardUpdates() SSE hook. The closest live freshness hooks are
123
+ // dashboardScreen.client.tsx's local data hooks plus useRelativeTime().
124
+ function useEventSourceUpdates(url: string) {
125
+ const [lastUpdate, setLastUpdate] = useState<unknown>(null);
126
+
127
+ useEffect(() => {
128
+ const source = new EventSource(url);
129
+
130
+ source.onmessage = (event) => {
131
+ setLastUpdate(JSON.parse(event.data));
132
+ };
133
+
134
+ source.onerror = () => {
135
+ console.warn('SSE connection lost, reconnecting...');
136
+ };
137
+
138
+ return () => source.close();
139
+ }, [url]);
140
+
141
+ return lastUpdate;
142
+ }
143
+ ```
144
+
145
+ ### 2. Smart Polling with Adaptive Intervals
146
+
147
+ When SSE is not viable (serverless, proxy limitations), use adaptive polling that adjusts frequency based on activity:
148
+
149
+ ```typescript
150
+ function useAdaptivePolling(fetchFn: () => Promise<Data>, options: {
151
+ activeInterval: number; // 5000ms when user is active
152
+ idleInterval: number; // 30000ms when tab is backgrounded
153
+ staleThreshold: number; // 60000ms before showing stale indicator
154
+ }) {
155
+ const [data, setData] = useState<Data | null>(null);
156
+ const [lastFetch, setLastFetch] = useState<Date>(new Date());
157
+ const [isStale, setIsStale] = useState(false);
158
+
159
+ useEffect(() => {
160
+ const interval = document.hidden
161
+ ? options.idleInterval
162
+ : options.activeInterval;
163
+
164
+ const timer = setInterval(async () => {
165
+ const result = await fetchFn();
166
+ setData(result);
167
+ setLastFetch(new Date());
168
+ setIsStale(false);
169
+ }, interval);
170
+
171
+ // Detect staleness
172
+ const staleChecker = setInterval(() => {
173
+ const elapsed = Date.now() - lastFetch.getTime();
174
+ setIsStale(elapsed > options.staleThreshold);
175
+ }, 5000);
176
+
177
+ return () => {
178
+ clearInterval(timer);
179
+ clearInterval(staleChecker);
180
+ };
181
+ }, [document.hidden]);
182
+
183
+ return { data, lastFetch, isStale };
184
+ }
185
+ ```
186
+
187
+ ### 3. Optimistic Updates
188
+
189
+ Show the result of a user action immediately, before the server confirms:
190
+
191
+ ```typescript
192
+ // Pattern: Optimistic update with rollback
193
+ async function updateOrderStatus(orderId: string, newStatus: string) {
194
+ // 1. Save previous state for rollback
195
+ const previousStatus = getOrderStatus(orderId);
196
+
197
+ // 2. Optimistically update UI
198
+ setOrderStatus(orderId, newStatus);
199
+
200
+ try {
201
+ // 3. Send to server
202
+ await api.updateOrderStatus(orderId, newStatus);
203
+ } catch (error) {
204
+ // 4. Rollback on failure
205
+ setOrderStatus(orderId, previousStatus);
206
+ showToast('Failed to update order status. Please try again.');
207
+ }
208
+ }
209
+ ```
210
+
211
+ **When to use optimistic updates:**
212
+
213
+ | Scenario | Use Optimistic? | Why |
214
+ |----------|----------------|-----|
215
+ | Toggle a setting | Yes | Low-risk, easily reversible |
216
+ | Mark order as shipped | Yes | Clear user intent, rollback is safe |
217
+ | Delete a record | No | Destructive, hard to undo visually |
218
+ | Financial calculation | No | Incorrect intermediate values mislead |
219
+ | Status filter change | Yes | UI-only, no data mutation |
220
+
221
+ ### 4. Stale Data Indicators
222
+
223
+ Every data display must communicate its freshness. Three patterns:
224
+
225
+ **Relative timestamp:** "Updated 2 minutes ago" — updates in real time via `useEffect` interval.
226
+
227
+ **Staleness badge:** A visual indicator that appears when data exceeds a freshness threshold:
228
+
229
+ | Data Type | Fresh Threshold | Stale Warning | Critical Stale |
230
+ |-----------|----------------|---------------|----------------|
231
+ | Order list | 5 minutes | "Data may be outdated" | "Last updated 30+ minutes ago" |
232
+ | Financial KPIs | 15 minutes | Subtle indicator | "Refresh for current data" |
233
+ | Settings | No staleness concern | N/A | N/A |
234
+
235
+ **Auto-refresh with notification:** When stale data is detected, offer the user a choice:
236
+
237
+ ```
238
+ [New data available. Refresh now | Dismiss]
239
+ ```
240
+
241
+ Do NOT auto-refresh without user consent if the user is interacting with the page (scrolling, filtering, selecting). This causes disorienting layout shifts.
242
+
243
+ ### 5. Reconnection Strategy
244
+
245
+ For persistent connections (SSE, WebSocket), implement reconnection with backoff:
246
+
247
+ ```
248
+ Disconnect detected
249
+ -> Wait 1s, attempt reconnect
250
+ -> If failed, wait 2s
251
+ -> If failed, wait 4s
252
+ -> If failed, wait 8s (cap)
253
+ -> Show "Connection lost. Retrying..." indicator to user
254
+ -> On reconnect, fetch missed updates (use last event ID or timestamp)
255
+ ```
256
+
257
+ **Critical:** After reconnection, the client may have missed updates. Fetch the delta between the last received event and the current state. Never assume the reconnected stream picks up exactly where it left off.
258
+
259
+ ### 6. Visibility API Integration
260
+
261
+ Stop polling and close SSE connections when the browser tab is not visible. Resume on focus:
262
+
263
+ ```typescript
264
+ document.addEventListener('visibilitychange', () => {
265
+ if (document.hidden) {
266
+ // Reduce polling frequency or pause SSE
267
+ pauseRealTimeUpdates();
268
+ } else {
269
+ // Resume and fetch any missed updates
270
+ resumeRealTimeUpdates();
271
+ fetchMissedUpdates();
272
+ }
273
+ });
274
+ ```
275
+
276
+ ## Anti-Patterns
277
+
278
+ 1. **WebSocket for server-to-client-only data.** Using WebSocket when data only flows from server to client. SSE is simpler, auto-reconnects, and works through HTTP/2 proxies without configuration.
279
+
280
+ 2. **Polling at fixed intervals regardless of activity.** Polling every 5 seconds even when the tab is backgrounded wastes bandwidth and server resources. Use the Visibility API to reduce or pause polling.
281
+
282
+ 3. **No staleness indicator.** Showing data that was fetched 10 minutes ago without any indication of age. The user assumes the data is current and makes decisions on stale information.
283
+
284
+ 4. **Auto-refreshing during user interaction.** Refreshing a data table while the user is selecting rows, scrolling, or editing inline. This causes layout shifts and data loss. Show a "New data available" banner instead.
285
+
286
+ 5. **Optimistic updates for destructive actions.** Showing a record as deleted before the server confirms. If the delete fails, the record "reappears," which is confusing and erodes trust.
287
+
288
+ 6. **No reconnection handling for SSE/WebSocket.** Assuming persistent connections never drop. Connections drop on network changes, server deploys, and proxy timeouts. Always implement reconnection with delta recovery.
289
+
290
+ 7. **Polling the same endpoint from multiple components.** Three components on the same page each polling `/api/orders` independently. Centralize polling in a shared hook or context provider.
291
+
292
+ ## Key Files
293
+
294
+ When working in a project with real-time updates:
295
+
296
+ - SSE endpoint routes — `api/events/` or `api/stream/`
297
+ - Live dashboard data hooks — `useOrders()`, `useRevenueDaily()`, `useRelativeTime()`
298
+ - Webhook handlers that trigger UI updates — webhook processing pipeline
299
+ - Stale data indicator components — timestamp display, staleness badges
300
+ - Polling configuration — intervals, visibility handling
301
+
302
+ ## Verification
303
+
304
+ After applying this skill, verify:
305
+ - [ ] Every data display communicates its freshness (timestamp or staleness indicator)
306
+ - [ ] Polling pauses or reduces frequency when the tab is not visible
307
+ - [ ] SSE/WebSocket connections implement reconnection with delta recovery
308
+ - [ ] Optimistic updates include rollback on server failure
309
+ - [ ] Auto-refresh does not occur during active user interaction
310
+ - [ ] The transport choice (SSE/WebSocket/polling) matches the data flow requirements
311
+ - [ ] Multiple components sharing the same data source use a centralized subscription
312
+
313
+ ## Do NOT Use When
314
+
315
+ | Instead of this skill | Use | Why |
316
+ |---|---|---|
317
+ | SSE implementation details and streaming protocols | `streaming` | Streaming covers the protocol; this skill covers the UX and architecture |
318
+ | Bidirectional real-time communication | `websocket` | WebSocket covers bidirectional patterns; this skill covers the decision framework |
319
+ | Data synchronization from external platforms | `data-sync` | Data sync covers webhook ingestion and reconciliation; this covers UI push |
320
+ | Scheduled periodic data refresh | `cron-scheduling` | Cron covers time-based triggers; this covers event-driven UI updates |
321
+
322
+ ---
323
+
324
+ *Version 1.0.0 -- 2026-03-29. Initial creation.*
@@ -0,0 +1,284 @@
1
+ ---
2
+ name: ref-patterns
3
+ description: "Use when designing or reviewing React ref usage: refs vs state (mutable handle vs reactive value), `useRef` for DOM access and mutable instance values, ref callbacks, `forwardRef` for passing refs through boundaries (and the React 19 ref-as-prop change), `useImperativeHandle` for controlled imperative surfaces, ref forwarding through compound-component primitives (Radix Slot / Headless UI), and the rule that refs are an escape hatch for DOM access, non-React library integration, focus management, animation, and imperative APIs — never a substitute for state. Do NOT use for hook discipline (use hooks-patterns), component layering (use component-architecture), state ownership (use state-management), client/server boundary (use client-server-boundary), or form-state patterns (use form-ux-architecture)."
4
+ license: MIT
5
+ allowed-tools: Read Grep
6
+ metadata:
7
+ metadata: "{\"schema_version\":6,\"version\":\"1.0.0\",\"type\":\"capability\",\"category\":\"engineering\",\"domain\":\"engineering/frontend\",\"scope\":\"reference\",\"owner\":\"skill-graph-maintainer\",\"freshness\":\"2026-05-17\",\"drift_check\":\"{\\\\\\\"last_verified\\\\\\\":\\\\\\\"2026-05-17\\\\\\\"}\",\"eval_artifacts\":\"planned\",\"eval_state\":\"unverified\",\"routing_eval\":\"absent\",\"comprehension_state\":\"present\",\"stability\":\"experimental\",\"keywords\":\"[\\\\\\\"useRef hook\\\\\\\",\\\\\\\"forwardRef\\\\\\\",\\\\\\\"useImperativeHandle\\\\\\\",\\\\\\\"ref callback\\\\\\\",\\\\\\\"DOM ref React\\\\\\\",\\\\\\\"ref forwarding compound component\\\\\\\",\\\\\\\"React 19 ref as prop\\\\\\\",\\\\\\\"mutable ref vs state\\\\\\\",\\\\\\\"current property ref\\\\\\\",\\\\\\\"Radix Slot ref forwarding\\\\\\\",\\\\\\\"focus management ref\\\\\\\",\\\\\\\"measure DOM node ref\\\\\\\",\\\\\\\"third-party DOM library ref integration\\\\\\\",\\\\\\\"escape hatch ref\\\\\\\"]\",\"triggers\":\"[\\\\\\\"how do I focus an input on mount\\\\\\\",\\\\\\\"how do I pass a ref through a wrapper component\\\\\\\",\\\\\\\"do I still need forwardRef in React 19\\\\\\\",\\\\\\\"when should I use a ref instead of state\\\\\\\",\\\\\\\"how do I expose a method like open or close to the parent\\\\\\\",\\\\\\\"how do I measure a DOM element\\\\\\\",\\\\\\\"why is my ref.current null on first render\\\\\\\",\\\\\\\"how do I integrate a non-React DOM library\\\\\\\"]\",\"examples\":\"[\\\\\\\"design a Modal component that exposes open() and close() to the parent via useImperativeHandle so the parent can trigger it imperatively without lifting full state\\\\\\\",\\\\\\\"forward a ref through a styled Button wrapper to the underlying <button> element using React 19 ref-as-prop (or forwardRef on React 18 and earlier)\\\\\\\",\\\\\\\"integrate a third-party chart library that needs a DOM container by handing it a ref callback that initializes on mount and tears down on unmount\\\\\\\",\\\\\\\"replace a useState that nobody reads in render with a useRef because the value drives an imperative side effect (interval id, latest-args closure) not the render output\\\\\\\",\\\\\\\"audit a component that uses a ref to read 'current form values' instead of reading from controlled state — usually a sign the wrong primitive was chosen\\\\\\\"]\",\"anti_examples\":\"[\\\\\\\"design the Rules of Hooks and dependency-array discipline for useEffect (use hooks-patterns)\\\\\\\",\\\\\\\"decide whether the form state lives in URL, server, client, or persistent storage (use state-management)\\\\\\\",\\\\\\\"design the headless-vs-styled layering of a component library (use component-architecture)\\\\\\\",\\\\\\\"explain how 'use client' marks a component boundary (use client-server-boundary)\\\\\\\",\\\\\\\"design the validation-state UX of an input (use form-ux-architecture)\\\\\\\",\\\\\\\"design the layering and API surface of a cross-product component library (use component-architecture)\\\\\\\",\\\\\\\"design the validation states, layout, and microcopy of a form (use form-ux-architecture)\\\\\\\"]\",\"relations\":\"{\\\\\\\"related\\\\\\\":[\\\\\\\"hooks-patterns\\\\\\\",\\\\\\\"component-architecture\\\\\\\",\\\\\\\"state-management\\\\\\\",\\\\\\\"client-server-boundary\\\\\\\",\\\\\\\"form-ux-architecture\\\\\\\"],\\\\\\\"boundary\\\\\\\":[{\\\\\\\"skill\\\\\\\":\\\\\\\"hooks-patterns\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"hooks-patterns owns the broader hook discipline — Rules of Hooks, dependency arrays, custom hooks, the You Might Not Need an Effect rule, the render/effect/cleanup mental model. ref-patterns covers the ref family specifically (useRef, forwardRef, useImperativeHandle, ref callbacks) and the design rule for when a ref is the right primitive vs when state is. They cross-reference but solve different problems.\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"state-management\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"state-management owns the location and ownership decisions for the four kinds of state (server / client UI / URL / persistent). ref-patterns is about the mutable-handle primitive that is NOT state — using a ref when a useState was needed (or vice versa) is the most common ref misuse. ref-patterns covers the boundary; state-management owns state itself.\\\\\\\"},{\\\\\\\"skill\\\\\\\":\\\\\\\"client-server-boundary\\\\\\\",\\\\\\\"reason\\\\\\\":\\\\\\\"client-server-boundary owns the serialization and directive mechanics. Refs only work in Client Components — a ref cannot be passed through a Server Component or serialized across the 'use client' boundary. ref-patterns notes this constraint; client-server-boundary owns the broader boundary semantics that explain why.\\\\\\\"}],\\\\\\\"verify_with\\\\\\\":[\\\\\\\"code-review\\\\\\\",\\\\\\\"hooks-patterns\\\\\\\"]}\",\"mental_model\":\"|\",\"purpose\":\"|\",\"boundary\":\"|\",\"analogy\":\"A ref is to a React component what a static local variable is to a C function — it persists across calls (renders), reading it does not make the function 'depend on' it, writing to it does not change the function's signature or trigger any caller-visible event, and its single purpose is to hold the state that is not part of the function's interface. State, by contrast, is to the component what the function's return value is to the caller: every read participates in the contract, every change requires a re-evaluation.\",\"misconception\":\"|\",\"concept\":\"{\\\\\\\"definition\\\\\\\":\\\\\\\"A React ref is a mutable object — `{ current: T }` — created by `useRef(initial)` that persists across renders without participating in the render cycle. Writing to `ref.current` does not trigger a re-render; reading from it does not subscribe the component to changes. The two canonical uses are (1) holding a reference to a DOM node so it can be focused, measured, or handed to a non-React library, and (2) holding a mutable value (an interval id, a latest-arguments closure, a previous-value snapshot) that drives side effects but is not part of what gets rendered. The design rule is: if the value should cause a re-render when it changes, it is state. If it should not, it is a ref.\\\\\\\",\\\\\\\"mental_model\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"purpose\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"boundary\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"taxonomy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"analogy\\\\\\\":\\\\\\\"|\\\\\\\",\\\\\\\"misconception\\\\\\\":\\\\\\\"|\\\\\\\"}\",\"skill_graph_source_repo\":\"https://github.com/jacob-balslev/skill-graph\",\"skill_graph_protocol\":\"Skill Metadata Protocol v5\",\"skill_graph_project\":\"Skill Graph\",\"skill_graph_canonical_skill\":\"skills/ref-patterns/SKILL.md\"}"
8
+ skill_graph_source_repo: "https://github.com/jacob-balslev/skill-graph"
9
+ skill_graph_protocol: Skill Metadata Protocol v4
10
+ skill_graph_project: Skill Graph
11
+ skill_graph_canonical_skill: skills/ref-patterns/SKILL.md
12
+ skill_graph_export_description: shortened for Agent Skills 1024-character description limit; canonical source keeps the full routing contract
13
+ skill_graph_canonical_description_length: "1311"
14
+ ---
15
+
16
+ # Ref Patterns
17
+
18
+ ## Coverage
19
+
20
+ The discipline of designing React ref usage: the conceptual distinction between refs (mutable handles that survive renders without triggering them) and state (reactive values that do trigger renders), the `useRef` hook for DOM access and mutable instance values, ref callbacks (`ref={(node) => ...}`) for fine-grained mount and unmount hooks, `forwardRef` for passing refs through component boundaries on React 18 and earlier, the React 19 **ref-as-prop** change that retires `forwardRef` for new code, `useImperativeHandle` for exposing a controlled imperative surface to a parent ref, the ref-forwarding pattern used inside compound-component primitives (Radix `Slot` / Headless UI), and the central design rule that refs are an escape hatch — appropriate for DOM access, focus management, animation, measurement, integration with non-React DOM libraries, and sparingly-exposed imperative APIs — never as a substitute for state.
21
+
22
+ ## Philosophy
23
+
24
+ React's rendering model is built on a single contract: the UI is a pure function of state and props. When state changes, the component re-renders; the new output is reconciled against the old; the DOM updates. This contract is what makes React components composable, testable, and reasonable.
25
+
26
+ Refs are the deliberate hole in that contract.
27
+
28
+ A ref is a mutable container — `{ current: T }` — that React creates once per component instance and preserves across renders. Writes to `ref.current` do not trigger a re-render. Reads from `ref.current` do not subscribe to changes. The ref exists outside the reactive graph.
29
+
30
+ That's exactly what makes refs useful in the few places they belong:
31
+
32
+ - A DOM node is an external resource that React produces but does not own. A ref gives the component a stable handle to that node for focus, measurement, animation, or third-party integration.
33
+ - An interval id, a `requestAnimationFrame` id, a previous-value snapshot, a "latest arguments" closure — these are values the component needs but should not render. Storing them in state would trigger pointless re-renders; storing them in refs keeps them out of the render path.
34
+ - A child component sometimes needs to expose an imperative surface (`open()`, `close()`, `focus()`, `scrollIntoView()`) that doesn't naturally fit into the props-and-state model. A ref combined with `useImperativeHandle` gives the parent that surface in a controlled way.
35
+
36
+ The same mutability that makes refs useful also makes them dangerous. **A ref used as a substitute for state silently breaks React's contract** — the value changes, the UI doesn't update, the bug surfaces somewhere remote from the cause. The design rule is sharp: *if the value should cause a re-render when it changes, it is state; if it should not, it is a ref.* No middle ground.
37
+
38
+ ## `useRef` — The Primitive
39
+
40
+ ```tsx
41
+ import { useRef, useEffect } from 'react'
42
+
43
+ function AutoFocusInput() {
44
+ const inputRef = useRef<HTMLInputElement>(null)
45
+
46
+ useEffect(() => {
47
+ inputRef.current?.focus()
48
+ }, [])
49
+
50
+ return <input ref={inputRef} />
51
+ }
52
+ ```
53
+
54
+ `useRef(initial)` returns the same `{ current }` object across every render of this component instance. The first render initializes `current` to the argument; subsequent renders return the same container with whatever `current` has become.
55
+
56
+ Two canonical shapes:
57
+
58
+ ```tsx
59
+ // DOM ref — initialized to null; React assigns the node on mount
60
+ const inputRef = useRef<HTMLInputElement>(null)
61
+
62
+ // Mutable instance value — initialized to a real value
63
+ const intervalIdRef = useRef<number | null>(null)
64
+ const latestPropsRef = useRef(props)
65
+ ```
66
+
67
+ In strict mode, refs survive React's intentional double-invocation of components and effects — they are tied to component instances, not to render passes.
68
+
69
+ ## The Ref / State Decision
70
+
71
+ | Symptom | Right primitive |
72
+ |---|---|
73
+ | The value drives what's rendered | `useState` |
74
+ | The value affects an effect's behavior but isn't shown | `useRef` (unless the effect needs to re-run when it changes, then `useState`) |
75
+ | You want a re-render when this changes | `useState` |
76
+ | You don't want a re-render when this changes | `useRef` |
77
+ | You're storing a DOM node | `useRef` (never `useState` — would trigger a re-render on every reconciliation pass) |
78
+ | You're storing a setTimeout / setInterval id | `useRef` |
79
+ | You're tracking "previous value" or "latest args" for an effect | `useRef` |
80
+ | You're caching an expensive value that depends on inputs | `useMemo` (not a ref) |
81
+ | You're holding form-input state | `useState` for controlled, `useRef` for genuinely uncontrolled |
82
+
83
+ The most common misuse: storing form values in refs to "avoid re-renders" — then needing to read those values during render and discovering the read is stale, because refs don't trigger renders when they change. The fix is almost always to use `useState`; the perf concern was a phantom.
84
+
85
+ ## Ref Callbacks
86
+
87
+ `ref` can be a callback instead of an object. The callback runs with the DOM node on mount, and with `null` on unmount:
88
+
89
+ ```tsx
90
+ function MeasuredDiv() {
91
+ const setRef = useCallback((node: HTMLDivElement | null) => {
92
+ if (node) {
93
+ const { width, height } = node.getBoundingClientRect()
94
+ // do something with width/height
95
+ } else {
96
+ // node is null — unmount cleanup
97
+ }
98
+ }, [])
99
+
100
+ return <div ref={setRef} />
101
+ }
102
+ ```
103
+
104
+ Use a ref callback when:
105
+
106
+ - You need to run code at the exact moment the DOM node mounts or unmounts (more reliable than `useEffect` for this — runs synchronously during commit).
107
+ - You need to attach the same ref to multiple elements conditionally (the callback is called with each).
108
+ - You need to compose multiple refs onto one element (write a `composeRefs` helper that calls each).
109
+
110
+ Composing refs is a common compound-component need:
111
+
112
+ ```tsx
113
+ function composeRefs<T>(...refs: (React.Ref<T> | undefined)[]) {
114
+ return (node: T | null) => {
115
+ for (const ref of refs) {
116
+ if (typeof ref === 'function') ref(node)
117
+ else if (ref) (ref as React.MutableRefObject<T | null>).current = node
118
+ }
119
+ }
120
+ }
121
+
122
+ // Usage in a Slot-style component:
123
+ <button ref={composeRefs(forwardedRef, internalRef)} />
124
+ ```
125
+
126
+ Libraries like Radix UI ship `composeRefs` as part of their primitives because compound components routinely need to merge a consumer's ref with the library's internal ref.
127
+
128
+ ## `forwardRef` — Pre-React-19
129
+
130
+ On React 18 and earlier, a function component cannot accept a `ref` prop directly — refs are special and need `forwardRef` to opt in:
131
+
132
+ ```tsx
133
+ import { forwardRef } from 'react'
134
+
135
+ const StyledButton = forwardRef<HTMLButtonElement, ButtonProps>(
136
+ function StyledButton(props, ref) {
137
+ return <button ref={ref} className="my-styled-button" {...props} />
138
+ },
139
+ )
140
+ ```
141
+
142
+ The consumer can now do `<StyledButton ref={myRef} />` and `myRef.current` will be the underlying `<button>` element. Without `forwardRef`, the same code would warn that "Function components cannot be given refs."
143
+
144
+ ## React 19 — Ref as Prop
145
+
146
+ In React 19, `ref` became a normal prop for function components. `forwardRef` is no longer required for new code:
147
+
148
+ ```tsx
149
+ // React 19
150
+ function StyledButton({ ref, ...props }: ButtonProps & { ref?: React.Ref<HTMLButtonElement> }) {
151
+ return <button ref={ref} className="my-styled-button" {...props} />
152
+ }
153
+ ```
154
+
155
+ `forwardRef` still works on React 19 for backward compatibility, but the React team's guidance is to use ref-as-prop for new code. Codemods exist to migrate existing `forwardRef` usage.
156
+
157
+ The change does not affect:
158
+
159
+ - Class components (still need `React.createRef` and instance refs).
160
+ - The fundamentals — refs still flow from consumer to provider; what changed is the API for accepting them, not the model.
161
+ - `useImperativeHandle` — still works the same way; the second argument to the function component is no longer the ref, so reach for the `ref` prop directly instead.
162
+
163
+ ## `useImperativeHandle` — The Controlled Imperative Surface
164
+
165
+ When a parent legitimately needs to call a method on a child (rare but real cases: focus an input from outside, scroll a virtualized list, open a modal), `useImperativeHandle` exposes a controlled object via the ref instead of the raw DOM node:
166
+
167
+ ```tsx
168
+ type ModalHandle = { open: () => void; close: () => void }
169
+
170
+ const Modal = forwardRef<ModalHandle, ModalProps>(function Modal(props, ref) {
171
+ const [isOpen, setIsOpen] = useState(false)
172
+
173
+ useImperativeHandle(ref, () => ({
174
+ open: () => setIsOpen(true),
175
+ close: () => setIsOpen(false),
176
+ }), [])
177
+
178
+ return isOpen ? <div role="dialog">{props.children}</div> : null
179
+ })
180
+
181
+ // Parent:
182
+ function Page() {
183
+ const modalRef = useRef<ModalHandle>(null)
184
+ return (
185
+ <>
186
+ <button onClick={() => modalRef.current?.open()}>Open</button>
187
+ <Modal ref={modalRef}>...</Modal>
188
+ </>
189
+ )
190
+ }
191
+ ```
192
+
193
+ Three discipline rules for `useImperativeHandle`:
194
+
195
+ 1. **Expose the minimum surface.** Don't return the full set of internal methods; return only what the parent needs. The parent's ref is a public API; treat it as one.
196
+ 2. **Don't expose state read functions.** If the parent needs to read state, lift state up. Reading via an imperative method makes the parent's render unreactive to that state.
197
+ 3. **Default to props.** Most cases that look like they need `useImperativeHandle` are better expressed as a `isOpen` prop with an `onOpenChange` callback — that puts the parent in control via the normal data flow.
198
+
199
+ The legitimate use cases are narrow: focus management on a wrapped input, scrolling a virtualized list to an index, triggering an animation on a complex sub-tree. Most other "imperative" needs reveal a state-design problem.
200
+
201
+ ## Ref Forwarding in Compound Components
202
+
203
+ Compound components (Radix UI, Headless UI, Reach UI, shadcn/ui) expose primitive parts (`Tabs.Root`, `Tabs.List`, `Tabs.Trigger`, `Tabs.Content`) that internally wrap real DOM elements. Consumers expect to forward refs to those underlying elements for focus, measurement, animation, and integration:
204
+
205
+ ```tsx
206
+ // Consumer code that expects ref forwarding to work:
207
+ const triggerRef = useRef<HTMLButtonElement>(null)
208
+ useEffect(() => { triggerRef.current?.focus() }, [])
209
+
210
+ <Tabs.Trigger ref={triggerRef} value="settings">Settings</Tabs.Trigger>
211
+ ```
212
+
213
+ For this to work, every layer between the consumer and the underlying `<button>` must forward the ref. Radix accomplishes this with a `Slot` component that merges a ref onto the rendered child:
214
+
215
+ ```tsx
216
+ // Sketch of how Radix's Slot pattern propagates refs through layers
217
+ <Tabs.Trigger> {/* accepts ref, forwards via Slot */}
218
+ <Slot ref={mergedRef}> {/* merges consumer ref with library internal ref */}
219
+ <button>...</button> {/* receives the merged ref */}
220
+ </Slot>
221
+ </Tabs.Trigger>
222
+ ```
223
+
224
+ The design rule for authoring compound components: **every primitive part must forward refs to its DOM node**. Failing to do this breaks consumer focus management, breaks third-party integrations that need DOM access, and produces "ref is null" bugs that are hard to diagnose because the chain of components looks correct.
225
+
226
+ A compound-component library that doesn't forward refs through every primitive is not finished.
227
+
228
+ ## Refs and the Client/Server Boundary
229
+
230
+ Refs only work in Client Components. A ref cannot:
231
+
232
+ - Be created inside a Server Component (no `useRef`).
233
+ - Be passed as a prop from a Server Component to a Client Component (refs are not serializable).
234
+ - Be returned from a Server Action (same reason).
235
+
236
+ Server Components produce HTML on the server; there is no DOM node to point at and no client-side runtime to hold the mutable container. If you need a ref, the component (or at least the part of the tree containing the ref) must be a Client Component marked with `'use client'`. The broader boundary mechanics belong to `client-server-boundary`; the consequence for refs is the constraint stated here.
237
+
238
+ ## Common Anti-Patterns
239
+
240
+ | Anti-pattern | Why it's wrong | Fix |
241
+ |---|---|---|
242
+ | Using a ref to store form input values to "avoid re-renders" | Reading the ref during render returns stale data; controlled inputs need state | Use `useState` — the perf concern was a phantom for almost all forms |
243
+ | Reading `ref.current` during render | The ref may be null on first render; render-time mutation breaks the render contract | Read refs inside effects, event handlers, or callbacks — never during render |
244
+ | Mutating a ref during render (`ref.current = newValue` inline) | Strict mode double-renders cause inconsistent state | Mutate inside effects, event handlers, or initial render guards (`if (ref.current === null)`) |
245
+ | `useImperativeHandle` exposing the whole internal API surface | Couples parent to internals; breaks encapsulation | Expose the minimum; prefer props + callbacks; reserve imperative for genuine escape-hatch needs |
246
+ | Compound-component primitive that doesn't forward refs | Consumer focus, measurement, third-party integration all break | Every primitive forwards refs to its underlying DOM element |
247
+ | Storing a DOM node in `useState` | Triggers re-render on every reconcile pass; pointless work | `useRef` for DOM nodes; or ref callback if mount/unmount hook is needed |
248
+ | Passing a ref through a Server Component | Refs are not serializable across the boundary | Mark the wrapper as `'use client'` so the ref flows in client-only space |
249
+ | Forgetting to clean up a ref-held resource (interval, observer, third-party instance) | Memory leak; observer fires after unmount | Clean up in the same effect or in the ref callback's `node === null` branch |
250
+ | Using `useRef` for a value derived from props | Stale closure; the value never updates | Either derive it inline in render (no ref needed) or sync via effect (`useEffect(() => { ref.current = derived }, [derived])`) — but the sync pattern is usually a smell |
251
+ | Replacing all `forwardRef` with ref-as-prop on a React 18 codebase | Breaks until React 19 is shipped | Migrate when React 19 is the active version; `forwardRef` still works on 18 |
252
+
253
+ ## Verification
254
+
255
+ After applying this skill, verify:
256
+
257
+ - [ ] Every ref is either a DOM ref or a mutable instance value — never a substitute for state that drives the UI.
258
+ - [ ] `ref.current` is only read inside effects, event handlers, or callbacks — never during render.
259
+ - [ ] Compound-component primitives forward refs to their underlying DOM elements.
260
+ - [ ] `useImperativeHandle` exposes the minimum surface needed; props + callbacks were considered first.
261
+ - [ ] No ref is being passed across the `'use client'` boundary as a prop from a Server Component.
262
+ - [ ] Ref-held resources (intervals, observers, third-party instances) have cleanup paths in effects or ref-callback null branches.
263
+ - [ ] On React 19, new code uses ref-as-prop; existing `forwardRef` is retained for backward compat or migrated via codemod.
264
+ - [ ] No `useState(domNode)` — DOM nodes go in refs, not state.
265
+ - [ ] Composed refs (multiple consumers attaching to one element) use a `composeRefs` helper rather than ad-hoc merging.
266
+
267
+ ## Grounding Sources
268
+
269
+ - React docs — [`useRef`](https://react.dev/reference/react/useRef). The hook reference, with the ref-vs-state decision tree.
270
+ - React docs — [Manipulating the DOM with Refs](https://react.dev/learn/manipulating-the-dom-with-refs). The conceptual introduction, with focus/scroll/measurement examples.
271
+ - React docs — [`forwardRef`](https://react.dev/reference/react/forwardRef) (deprecated as of React 19) and the [ref-as-prop migration note](https://react.dev/blog/2024/12/05/react-19#ref-as-a-prop). The pre- and post-React-19 API.
272
+ - React docs — [`useImperativeHandle`](https://react.dev/reference/react/useImperativeHandle). The controlled imperative surface API.
273
+ - Radix UI — [Composition (Slot)](https://www.radix-ui.com/primitives/docs/utilities/slot). The canonical ref-forwarding pattern for compound primitives.
274
+ - Dodds, K. — [How to use React Context effectively](https://kentcdodds.com/blog/how-to-use-react-context-effectively) (covers the "lift imperative methods into props" alternative to `useImperativeHandle`).
275
+
276
+ ## Do NOT Use When
277
+
278
+ | Instead of this skill | Use | Why |
279
+ |---|---|---|
280
+ | The broader hook discipline — Rules of Hooks, dependency arrays, custom hooks, You Might Not Need an Effect | `hooks-patterns` | hooks-patterns owns the hook model; this skill covers the ref family specifically. |
281
+ | The cross-product component-layering question — primitives vs composites, headless vs styled, controlled vs uncontrolled | `component-architecture` | component-architecture owns the layering; this skill covers the ref-forwarding mechanics that compound primitives need. |
282
+ | State location and ownership decisions for server / client / URL / persistent state | `state-management` | state-management owns state; this skill owns the not-state primitive and the decision boundary between them. |
283
+ | The serialization and directive mechanics of `'use client'` and `'use server'` | `client-server-boundary` | client-server-boundary owns the boundary; this skill notes the constraint that refs can't cross it. |
284
+ | Form-state design — validation, accessibility, microcopy, layout | `form-ux-architecture` | form-ux-architecture owns forms; refs may appear in forms (focus management, uncontrolled inputs) but the broader design belongs there. |