@codyswann/lisa 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (322) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +867 -0
  3. package/all/copy-overwrite/.claude/README.md +205 -0
  4. package/all/copy-overwrite/.claude/agents/agent-architect.md +311 -0
  5. package/all/copy-overwrite/.claude/agents/codebase-analyzer.md +146 -0
  6. package/all/copy-overwrite/.claude/agents/codebase-locator.md +125 -0
  7. package/all/copy-overwrite/.claude/agents/codebase-pattern-finder.md +237 -0
  8. package/all/copy-overwrite/.claude/agents/git-history-analyzer.md +183 -0
  9. package/all/copy-overwrite/.claude/agents/hooks-expert.md +74 -0
  10. package/all/copy-overwrite/.claude/agents/skill-evaluator.md +246 -0
  11. package/all/copy-overwrite/.claude/agents/slash-command-architect.md +87 -0
  12. package/all/copy-overwrite/.claude/agents/web-search-researcher.md +112 -0
  13. package/all/copy-overwrite/.claude/commands/git/commit-and-submit-pr.md +8 -0
  14. package/all/copy-overwrite/.claude/commands/git/commit.md +44 -0
  15. package/all/copy-overwrite/.claude/commands/git/prune.md +34 -0
  16. package/all/copy-overwrite/.claude/commands/git/submit-pr.md +50 -0
  17. package/all/copy-overwrite/.claude/commands/jira/create.md +50 -0
  18. package/all/copy-overwrite/.claude/commands/jira/verify.md +34 -0
  19. package/all/copy-overwrite/.claude/commands/project/archive.md +8 -0
  20. package/all/copy-overwrite/.claude/commands/project/bootstrap.md +49 -0
  21. package/all/copy-overwrite/.claude/commands/project/complete-task.md +7 -0
  22. package/all/copy-overwrite/.claude/commands/project/debrief.md +65 -0
  23. package/all/copy-overwrite/.claude/commands/project/execute.md +94 -0
  24. package/all/copy-overwrite/.claude/commands/project/implement.md +42 -0
  25. package/all/copy-overwrite/.claude/commands/project/local-code-review.md +88 -0
  26. package/all/copy-overwrite/.claude/commands/project/lower-code-complexity.md +74 -0
  27. package/all/copy-overwrite/.claude/commands/project/plan.md +314 -0
  28. package/all/copy-overwrite/.claude/commands/project/research.md +248 -0
  29. package/all/copy-overwrite/.claude/commands/project/review.md +63 -0
  30. package/all/copy-overwrite/.claude/commands/project/setup.md +19 -0
  31. package/all/copy-overwrite/.claude/commands/project/verify.md +38 -0
  32. package/all/copy-overwrite/.claude/commands/pull-request/review.md +12 -0
  33. package/all/copy-overwrite/.claude/commands/rules/format-md.md +72 -0
  34. package/all/copy-overwrite/.claude/commands/sonarqube/check.md +6 -0
  35. package/all/copy-overwrite/.claude/commands/sonarqube/fix.md +3 -0
  36. package/all/copy-overwrite/.claude/hooks/README.md +301 -0
  37. package/all/copy-overwrite/.claude/hooks/notify-ntfy.sh +181 -0
  38. package/all/copy-overwrite/.claude/settings.json +41 -0
  39. package/all/copy-overwrite/.claude/settings.local.json.example +14 -0
  40. package/all/copy-overwrite/.claude/skills/coding-philosophy/SKILL.md +405 -0
  41. package/all/copy-overwrite/.claude/skills/coding-philosophy/references/function-structure.md +416 -0
  42. package/all/copy-overwrite/.claude/skills/coding-philosophy/references/immutable-patterns.md +316 -0
  43. package/all/copy-overwrite/.claude/skills/prompt-complexity-scorer/SKILL.md +118 -0
  44. package/all/copy-overwrite/.claude/skills/skill-creator/LICENSE.txt +202 -0
  45. package/all/copy-overwrite/.claude/skills/skill-creator/SKILL.md +210 -0
  46. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/__pycache__/quick_validate.cpython-312.pyc +0 -0
  47. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/init_skill.py +303 -0
  48. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/package_skill.py +110 -0
  49. package/all/copy-overwrite/.claude/skills/skill-creator/scripts/quick_validate.py +65 -0
  50. package/all/copy-overwrite/CLAUDE.md +77 -0
  51. package/all/copy-overwrite/HUMAN.md +17 -0
  52. package/all/copy-overwrite/specs/.keep +0 -0
  53. package/all/create-only/PROJECT_RULES.md +0 -0
  54. package/cdk/merge/package.json +20 -0
  55. package/dist/cli/index.d.ts +7 -0
  56. package/dist/cli/index.d.ts.map +1 -0
  57. package/dist/cli/index.js +107 -0
  58. package/dist/cli/index.js.map +1 -0
  59. package/dist/cli/prompts.d.ts +45 -0
  60. package/dist/cli/prompts.d.ts.map +1 -0
  61. package/dist/cli/prompts.js +58 -0
  62. package/dist/cli/prompts.js.map +1 -0
  63. package/dist/core/config.d.ts +73 -0
  64. package/dist/core/config.d.ts.map +1 -0
  65. package/dist/core/config.js +36 -0
  66. package/dist/core/config.js.map +1 -0
  67. package/dist/core/index.d.ts +4 -0
  68. package/dist/core/index.d.ts.map +1 -0
  69. package/dist/core/index.js +4 -0
  70. package/dist/core/index.js.map +1 -0
  71. package/dist/core/lisa.d.ts +81 -0
  72. package/dist/core/lisa.d.ts.map +1 -0
  73. package/dist/core/lisa.js +459 -0
  74. package/dist/core/lisa.js.map +1 -0
  75. package/dist/core/manifest.d.ts +58 -0
  76. package/dist/core/manifest.d.ts.map +1 -0
  77. package/dist/core/manifest.js +104 -0
  78. package/dist/core/manifest.js.map +1 -0
  79. package/dist/detection/detector.interface.d.ts +15 -0
  80. package/dist/detection/detector.interface.d.ts.map +1 -0
  81. package/dist/detection/detector.interface.js +2 -0
  82. package/dist/detection/detector.interface.js.map +1 -0
  83. package/dist/detection/detectors/cdk.d.ts +10 -0
  84. package/dist/detection/detectors/cdk.d.ts.map +1 -0
  85. package/dist/detection/detectors/cdk.js +34 -0
  86. package/dist/detection/detectors/cdk.js.map +1 -0
  87. package/dist/detection/detectors/expo.d.ts +10 -0
  88. package/dist/detection/detectors/expo.d.ts.map +1 -0
  89. package/dist/detection/detectors/expo.js +30 -0
  90. package/dist/detection/detectors/expo.js.map +1 -0
  91. package/dist/detection/detectors/nestjs.d.ts +10 -0
  92. package/dist/detection/detectors/nestjs.d.ts.map +1 -0
  93. package/dist/detection/detectors/nestjs.js +34 -0
  94. package/dist/detection/detectors/nestjs.js.map +1 -0
  95. package/dist/detection/detectors/npm-package.d.ts +13 -0
  96. package/dist/detection/detectors/npm-package.d.ts.map +1 -0
  97. package/dist/detection/detectors/npm-package.js +30 -0
  98. package/dist/detection/detectors/npm-package.js.map +1 -0
  99. package/dist/detection/detectors/typescript.d.ts +10 -0
  100. package/dist/detection/detectors/typescript.d.ts.map +1 -0
  101. package/dist/detection/detectors/typescript.js +25 -0
  102. package/dist/detection/detectors/typescript.js.map +1 -0
  103. package/dist/detection/index.d.ts +24 -0
  104. package/dist/detection/index.d.ts.map +1 -0
  105. package/dist/detection/index.js +57 -0
  106. package/dist/detection/index.js.map +1 -0
  107. package/dist/errors/index.d.ts +69 -0
  108. package/dist/errors/index.d.ts.map +1 -0
  109. package/dist/errors/index.js +110 -0
  110. package/dist/errors/index.js.map +1 -0
  111. package/dist/index.d.ts +3 -0
  112. package/dist/index.d.ts.map +1 -0
  113. package/dist/index.js +8 -0
  114. package/dist/index.js.map +1 -0
  115. package/dist/logging/console-logger.d.ts +12 -0
  116. package/dist/logging/console-logger.d.ts.map +1 -0
  117. package/dist/logging/console-logger.js +22 -0
  118. package/dist/logging/console-logger.js.map +1 -0
  119. package/dist/logging/index.d.ts +4 -0
  120. package/dist/logging/index.d.ts.map +1 -0
  121. package/dist/logging/index.js +3 -0
  122. package/dist/logging/index.js.map +1 -0
  123. package/dist/logging/logger.interface.d.ts +20 -0
  124. package/dist/logging/logger.interface.d.ts.map +1 -0
  125. package/dist/logging/logger.interface.js +2 -0
  126. package/dist/logging/logger.interface.js.map +1 -0
  127. package/dist/logging/silent-logger.d.ts +12 -0
  128. package/dist/logging/silent-logger.d.ts.map +1 -0
  129. package/dist/logging/silent-logger.js +21 -0
  130. package/dist/logging/silent-logger.js.map +1 -0
  131. package/dist/strategies/copy-contents.d.ts +14 -0
  132. package/dist/strategies/copy-contents.d.ts.map +1 -0
  133. package/dist/strategies/copy-contents.js +69 -0
  134. package/dist/strategies/copy-contents.js.map +1 -0
  135. package/dist/strategies/copy-overwrite.d.ts +14 -0
  136. package/dist/strategies/copy-overwrite.d.ts.map +1 -0
  137. package/dist/strategies/copy-overwrite.js +47 -0
  138. package/dist/strategies/copy-overwrite.js.map +1 -0
  139. package/dist/strategies/create-only.d.ts +13 -0
  140. package/dist/strategies/create-only.d.ts.map +1 -0
  141. package/dist/strategies/create-only.js +30 -0
  142. package/dist/strategies/create-only.js.map +1 -0
  143. package/dist/strategies/index.d.ts +31 -0
  144. package/dist/strategies/index.d.ts.map +1 -0
  145. package/dist/strategies/index.js +52 -0
  146. package/dist/strategies/index.js.map +1 -0
  147. package/dist/strategies/merge.d.ts +13 -0
  148. package/dist/strategies/merge.d.ts.map +1 -0
  149. package/dist/strategies/merge.js +60 -0
  150. package/dist/strategies/merge.js.map +1 -0
  151. package/dist/strategies/strategy.interface.d.ts +31 -0
  152. package/dist/strategies/strategy.interface.d.ts.map +1 -0
  153. package/dist/strategies/strategy.interface.js +2 -0
  154. package/dist/strategies/strategy.interface.js.map +1 -0
  155. package/dist/transaction/backup.d.ts +38 -0
  156. package/dist/transaction/backup.d.ts.map +1 -0
  157. package/dist/transaction/backup.js +97 -0
  158. package/dist/transaction/backup.js.map +1 -0
  159. package/dist/transaction/index.d.ts +4 -0
  160. package/dist/transaction/index.d.ts.map +1 -0
  161. package/dist/transaction/index.js +3 -0
  162. package/dist/transaction/index.js.map +1 -0
  163. package/dist/transaction/transaction.d.ts +34 -0
  164. package/dist/transaction/transaction.d.ts.map +1 -0
  165. package/dist/transaction/transaction.js +68 -0
  166. package/dist/transaction/transaction.js.map +1 -0
  167. package/dist/utils/file-operations.d.ts +29 -0
  168. package/dist/utils/file-operations.d.ts.map +1 -0
  169. package/dist/utils/file-operations.js +84 -0
  170. package/dist/utils/file-operations.js.map +1 -0
  171. package/dist/utils/index.d.ts +4 -0
  172. package/dist/utils/index.d.ts.map +1 -0
  173. package/dist/utils/index.js +4 -0
  174. package/dist/utils/index.js.map +1 -0
  175. package/dist/utils/json-utils.d.ts +22 -0
  176. package/dist/utils/json-utils.d.ts.map +1 -0
  177. package/dist/utils/json-utils.js +57 -0
  178. package/dist/utils/json-utils.js.map +1 -0
  179. package/dist/utils/path-utils.d.ts +21 -0
  180. package/dist/utils/path-utils.d.ts.map +1 -0
  181. package/dist/utils/path-utils.js +35 -0
  182. package/dist/utils/path-utils.js.map +1 -0
  183. package/eslint-plugin-code-organization/README.md +149 -0
  184. package/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
  185. package/eslint-plugin-code-organization/index.js +23 -0
  186. package/eslint-plugin-code-organization/package.json +10 -0
  187. package/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
  188. package/expo/copy-overwrite/.claude/skills/apollo-client/SKILL.md +238 -0
  189. package/expo/copy-overwrite/.claude/skills/apollo-client/references/mutation-patterns.md +360 -0
  190. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/SKILL.md +360 -0
  191. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/atomic-levels.md +417 -0
  192. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/folder-structure.md +257 -0
  193. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/references/gluestack-mapping.md +233 -0
  194. package/expo/copy-overwrite/.claude/skills/atomic-design-gluestack/scripts/validate_atomic_structure.py +327 -0
  195. package/expo/copy-overwrite/.claude/skills/container-view-pattern/SKILL.md +299 -0
  196. package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/examples.md +749 -0
  197. package/expo/copy-overwrite/.claude/skills/container-view-pattern/references/patterns.md +318 -0
  198. package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/create_component.py +198 -0
  199. package/expo/copy-overwrite/.claude/skills/container-view-pattern/scripts/validate_component.py +207 -0
  200. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/SKILL.md +268 -0
  201. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/common-issues.md +619 -0
  202. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/file-extensions.md +340 -0
  203. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/references/platform-api.md +276 -0
  204. package/expo/copy-overwrite/.claude/skills/cross-platform-compatibility/scripts/validate_cross_platform.py +414 -0
  205. package/expo/copy-overwrite/.claude/skills/directory-structure/SKILL.md +202 -0
  206. package/expo/copy-overwrite/.claude/skills/directory-structure/scripts/validate_structure.py +443 -0
  207. package/expo/copy-overwrite/.claude/skills/expo-env-config/SKILL.md +309 -0
  208. package/expo/copy-overwrite/.claude/skills/expo-env-config/references/validation-patterns.md +417 -0
  209. package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/SKILL.md +431 -0
  210. package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/references/official-docs.md +290 -0
  211. package/expo/copy-overwrite/.claude/skills/expo-router-best-practices/scripts/generate-route.py +169 -0
  212. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/SKILL.md +411 -0
  213. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/color-tokens.md +343 -0
  214. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/component-mapping.md +307 -0
  215. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/references/spacing-scale.md +300 -0
  216. package/expo/copy-overwrite/.claude/skills/gluestack-nativewind/scripts/validate_styling.py +354 -0
  217. package/expo/copy-overwrite/.claude/skills/local-state/SKILL.md +362 -0
  218. package/expo/copy-overwrite/.claude/skills/local-state/references/async-storage.md +505 -0
  219. package/expo/copy-overwrite/.claude/skills/local-state/references/persistence-patterns.md +711 -0
  220. package/expo/copy-overwrite/.claude/skills/local-state/references/reactive-variables.md +446 -0
  221. package/expo/copy-overwrite/.claude/skills/playwright-selectors/SKILL.md +223 -0
  222. package/expo/copy-overwrite/.claude/skills/testing-library/SKILL.md +319 -0
  223. package/expo/copy-overwrite/.claude/skills/testing-library/references/async-patterns.md +420 -0
  224. package/expo/copy-overwrite/.claude/skills/testing-library/references/expo-router-testing.md +556 -0
  225. package/expo/copy-overwrite/.claude/skills/testing-library/references/mocking-patterns.md +590 -0
  226. package/expo/copy-overwrite/.claude/skills/testing-library/references/query-priority.md +291 -0
  227. package/expo/copy-overwrite/.easignore.extra +2 -0
  228. package/expo/copy-overwrite/.mcp.json +33 -0
  229. package/expo/copy-overwrite/eslint-plugin-component-structure/README.md +234 -0
  230. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/plugin-index.test.js +84 -0
  231. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/require-memo-in-view.test.js +196 -0
  232. package/expo/copy-overwrite/eslint-plugin-component-structure/__tests__/single-component-per-file.test.js +289 -0
  233. package/expo/copy-overwrite/eslint-plugin-component-structure/index.js +32 -0
  234. package/expo/copy-overwrite/eslint-plugin-component-structure/package.json +10 -0
  235. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/enforce-component-structure.js +230 -0
  236. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/no-return-in-view.js +91 -0
  237. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/require-memo-in-view.js +178 -0
  238. package/expo/copy-overwrite/eslint-plugin-component-structure/rules/single-component-per-file.js +238 -0
  239. package/expo/copy-overwrite/eslint-plugin-ui-standards/README.md +260 -0
  240. package/expo/copy-overwrite/eslint-plugin-ui-standards/index.js +29 -0
  241. package/expo/copy-overwrite/eslint-plugin-ui-standards/package.json +10 -0
  242. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-classname-outside-ui.js +51 -0
  243. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-direct-rn-imports.js +55 -0
  244. package/expo/copy-overwrite/eslint-plugin-ui-standards/rules/no-inline-styles.js +73 -0
  245. package/expo/copy-overwrite/eslint.config.mjs +560 -0
  246. package/expo/copy-overwrite/lighthouserc.js +194 -0
  247. package/expo/create-only/lighthouserc-config.json +28 -0
  248. package/expo/merge/package.json +132 -0
  249. package/lisa.sh +35 -0
  250. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/SKILL.md +176 -0
  251. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/advanced-features.md +527 -0
  252. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/project-patterns.md +483 -0
  253. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/quick-start.md +257 -0
  254. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/resolvers-mutations.md +413 -0
  255. package/nestjs/copy-overwrite/.claude/skills/nestjs-graphql/references/types-scalars.md +513 -0
  256. package/nestjs/copy-overwrite/.claude/skills/nestjs-rules/SKILL.md +536 -0
  257. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/SKILL.md +275 -0
  258. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/configuration-patterns.md +487 -0
  259. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/entity-patterns.md +450 -0
  260. package/nestjs/copy-overwrite/.claude/skills/typeorm-patterns/references/observability-patterns.md +536 -0
  261. package/nestjs/merge/package.json +75 -0
  262. package/package.json +124 -0
  263. package/typescript/copy-contents/.husky/commit-msg +91 -0
  264. package/typescript/copy-contents/.husky/pre-commit +96 -0
  265. package/typescript/copy-contents/.husky/pre-push +211 -0
  266. package/typescript/copy-overwrite/.claude/hooks/format-on-edit.sh +74 -0
  267. package/typescript/copy-overwrite/.claude/hooks/install_pkgs.sh +59 -0
  268. package/typescript/copy-overwrite/.claude/hooks/lint-on-edit.sh +103 -0
  269. package/typescript/copy-overwrite/.claude/skills/jsdoc-best-practices/SKILL.md +388 -0
  270. package/typescript/copy-overwrite/.github/README.md +455 -0
  271. package/typescript/copy-overwrite/.github/dependabot.yml +40 -0
  272. package/typescript/copy-overwrite/.github/k6/BROWSER_TESTING_NOTE.md +129 -0
  273. package/typescript/copy-overwrite/.github/k6/INTEGRATION_GUIDE.md +354 -0
  274. package/typescript/copy-overwrite/.github/k6/README.md +386 -0
  275. package/typescript/copy-overwrite/.github/k6/SCENARIO_SELECTION_GUIDE.md +264 -0
  276. package/typescript/copy-overwrite/.github/k6/examples/customer-deploy-integration.yml +115 -0
  277. package/typescript/copy-overwrite/.github/k6/examples/data-driven-test.js +268 -0
  278. package/typescript/copy-overwrite/.github/k6/scenarios/load.js +142 -0
  279. package/typescript/copy-overwrite/.github/k6/scenarios/load.json +27 -0
  280. package/typescript/copy-overwrite/.github/k6/scenarios/smoke.js +26 -0
  281. package/typescript/copy-overwrite/.github/k6/scenarios/smoke.json +20 -0
  282. package/typescript/copy-overwrite/.github/k6/scenarios/soak.js +244 -0
  283. package/typescript/copy-overwrite/.github/k6/scenarios/soak.json +29 -0
  284. package/typescript/copy-overwrite/.github/k6/scenarios/spike.js +180 -0
  285. package/typescript/copy-overwrite/.github/k6/scenarios/spike.json +32 -0
  286. package/typescript/copy-overwrite/.github/k6/scenarios/stress.js +206 -0
  287. package/typescript/copy-overwrite/.github/k6/scenarios/stress.json +38 -0
  288. package/typescript/copy-overwrite/.github/k6/scripts/api-test.js +452 -0
  289. package/typescript/copy-overwrite/.github/k6/scripts/default-test.js +185 -0
  290. package/typescript/copy-overwrite/.github/k6/thresholds/normal.json +30 -0
  291. package/typescript/copy-overwrite/.github/k6/thresholds/relaxed.json +21 -0
  292. package/typescript/copy-overwrite/.github/k6/thresholds/strict.json +29 -0
  293. package/typescript/copy-overwrite/.github/workflows/build.yml +72 -0
  294. package/typescript/copy-overwrite/.github/workflows/ci.yml +49 -0
  295. package/typescript/copy-overwrite/.github/workflows/claude.yml +51 -0
  296. package/typescript/copy-overwrite/.github/workflows/create-github-issue-on-failure.yml +113 -0
  297. package/typescript/copy-overwrite/.github/workflows/create-jira-issue-on-failure.yml +195 -0
  298. package/typescript/copy-overwrite/.github/workflows/create-sentry-issue-on-failure.yml +267 -0
  299. package/typescript/copy-overwrite/.github/workflows/deploy.yml +228 -0
  300. package/typescript/copy-overwrite/.github/workflows/k6-load-test-README.md +230 -0
  301. package/typescript/copy-overwrite/.github/workflows/lighthouse.yml +68 -0
  302. package/typescript/copy-overwrite/.github/workflows/load-test.yml +282 -0
  303. package/typescript/copy-overwrite/.github/workflows/quality.yml +1737 -0
  304. package/typescript/copy-overwrite/.github/workflows/release.yml +1599 -0
  305. package/typescript/copy-overwrite/.gitleaksignore +28 -0
  306. package/typescript/copy-overwrite/.nvmrc +1 -0
  307. package/typescript/copy-overwrite/.prettierignore +23 -0
  308. package/typescript/copy-overwrite/.prettierrc.json +22 -0
  309. package/typescript/copy-overwrite/.versionrc +42 -0
  310. package/typescript/copy-overwrite/.yamllint +20 -0
  311. package/typescript/copy-overwrite/commitlint.config.js +11 -0
  312. package/typescript/copy-overwrite/eslint-plugin-code-organization/README.md +149 -0
  313. package/typescript/copy-overwrite/eslint-plugin-code-organization/__tests__/enforce-statement-order.test.js +468 -0
  314. package/typescript/copy-overwrite/eslint-plugin-code-organization/index.js +23 -0
  315. package/typescript/copy-overwrite/eslint-plugin-code-organization/package.json +10 -0
  316. package/typescript/copy-overwrite/eslint-plugin-code-organization/rules/enforce-statement-order.js +157 -0
  317. package/typescript/copy-overwrite/eslint.config.mjs +390 -0
  318. package/typescript/copy-overwrite/eslint.ignore.config.json +57 -0
  319. package/typescript/copy-overwrite/eslint.thresholds.config.json +5 -0
  320. package/typescript/github-rulesets/base.json +106 -0
  321. package/typescript/merge/.claude/settings.json +28 -0
  322. package/typescript/merge/package.json +71 -0
@@ -0,0 +1,711 @@
1
+ # Persistence Patterns Reference
2
+
3
+ ## Overview
4
+
5
+ This reference covers patterns for persisting reactive variables to AsyncStorage, ensuring that local state survives app restarts while maintaining reactive UI updates.
6
+
7
+ ## Why Persistence is Needed
8
+
9
+ Reactive variables are in-memory only. When the app restarts:
10
+
11
+ - All reactive variable values reset to their defaults
12
+ - User preferences are lost
13
+ - Feature flag states reset
14
+ - Filter selections disappear
15
+
16
+ Persistence bridges this gap by saving state to AsyncStorage and restoring it on app launch.
17
+
18
+ ## Pattern 1: Immediate Persistence
19
+
20
+ Update storage immediately on every change. Best for critical user preferences that must never be lost.
21
+
22
+ ### Implementation
23
+
24
+ ```typescript
25
+ import AsyncStorage from "@react-native-async-storage/async-storage";
26
+ import { makeVar, useReactiveVar } from "@apollo/client";
27
+ import { useCallback, useEffect } from "react";
28
+
29
+ // Types
30
+ interface IUserPreferences {
31
+ readonly theme: "light" | "dark";
32
+ readonly language: string;
33
+ readonly notifications: boolean;
34
+ }
35
+
36
+ // Constants
37
+ const STORAGE_KEY = "@whatever:user-preferences";
38
+ const DEFAULT_PREFERENCES: IUserPreferences = {
39
+ theme: "light",
40
+ language: "en",
41
+ notifications: true,
42
+ };
43
+
44
+ // Reactive Variable
45
+ export const userPreferencesVar =
46
+ makeVar<IUserPreferences>(DEFAULT_PREFERENCES);
47
+
48
+ /**
49
+ * Update preferences and immediately persist to storage
50
+ * @param updates - Partial preferences to merge
51
+ */
52
+ const updatePreferences = async (
53
+ updates: Partial<IUserPreferences>
54
+ ): Promise<void> => {
55
+ const current = userPreferencesVar();
56
+ const newPrefs: IUserPreferences = { ...current, ...updates };
57
+
58
+ // Update reactive variable first (immediate UI update)
59
+ userPreferencesVar(newPrefs);
60
+
61
+ // Persist to storage (async, but starts immediately)
62
+ try {
63
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newPrefs));
64
+ } catch (error) {
65
+ const message = error instanceof Error ? error.message : "Unknown error";
66
+ console.error("Failed to persist preferences:", message);
67
+ // Note: UI already updated, storage just failed to persist
68
+ }
69
+ };
70
+
71
+ /**
72
+ * Load persisted preferences on app start
73
+ */
74
+ export const loadUserPreferences = async (): Promise<void> => {
75
+ try {
76
+ const stored = await AsyncStorage.getItem(STORAGE_KEY);
77
+ if (stored) {
78
+ const parsed = JSON.parse(stored) as Partial<IUserPreferences>;
79
+ // Merge with defaults to handle missing fields from older versions
80
+ userPreferencesVar({ ...DEFAULT_PREFERENCES, ...parsed });
81
+ }
82
+ } catch (error) {
83
+ const message = error instanceof Error ? error.message : "Unknown error";
84
+ console.error("Failed to load preferences:", message);
85
+ }
86
+ };
87
+
88
+ /**
89
+ * Custom hook for consuming and updating preferences
90
+ */
91
+ export const useUserPreferences = () => {
92
+ const preferences = useReactiveVar(userPreferencesVar);
93
+
94
+ const setTheme = useCallback((theme: "light" | "dark") => {
95
+ updatePreferences({ theme });
96
+ }, []);
97
+
98
+ const setLanguage = useCallback((language: string) => {
99
+ updatePreferences({ language });
100
+ }, []);
101
+
102
+ const setNotifications = useCallback((notifications: boolean) => {
103
+ updatePreferences({ notifications });
104
+ }, []);
105
+
106
+ return {
107
+ preferences,
108
+ setTheme,
109
+ setLanguage,
110
+ setNotifications,
111
+ };
112
+ };
113
+ ```
114
+
115
+ ### App Initialization
116
+
117
+ ```typescript
118
+ // In root layout or app entry
119
+ import { useEffect } from "react";
120
+ import { loadUserPreferences } from "@/features/settings/stores/userPreferences";
121
+
122
+ export default function RootLayout() {
123
+ useEffect(() => {
124
+ loadUserPreferences();
125
+ }, []);
126
+
127
+ return (
128
+ // ... app content
129
+ );
130
+ }
131
+ ```
132
+
133
+ ### When to Use
134
+
135
+ - User preferences that are critical
136
+ - Settings that affect app behavior immediately
137
+ - Data that users expect to persist always
138
+
139
+ ### Trade-offs
140
+
141
+ - **Pro**: Data is always persisted, minimal data loss risk
142
+ - **Con**: More storage operations, potential performance impact with frequent changes
143
+
144
+ ## Pattern 2: AppState-Based Persistence
145
+
146
+ Save to storage when app goes to background. Better for non-critical state that changes frequently.
147
+
148
+ ### Implementation
149
+
150
+ ```typescript
151
+ import AsyncStorage from "@react-native-async-storage/async-storage";
152
+ import { makeVar, useReactiveVar } from "@apollo/client";
153
+ import { useEffect, useCallback } from "react";
154
+ import { AppState, AppStateStatus } from "react-native";
155
+
156
+ // Types
157
+ interface IFilterState {
158
+ readonly minAge: number;
159
+ readonly maxAge: number;
160
+ readonly positions: readonly string[];
161
+ readonly teams: readonly string[];
162
+ }
163
+
164
+ // Constants
165
+ const STORAGE_KEY = "@whatever:filter-state";
166
+ const DEFAULT_FILTERS: IFilterState = {
167
+ minAge: 18,
168
+ maxAge: 40,
169
+ positions: [],
170
+ teams: [],
171
+ };
172
+
173
+ // Reactive Variable
174
+ export const filterStateVar = makeVar<IFilterState>(DEFAULT_FILTERS);
175
+
176
+ /**
177
+ * Load persisted filter state on app start
178
+ */
179
+ const loadFilterState = async (): Promise<void> => {
180
+ try {
181
+ const stored = await AsyncStorage.getItem(STORAGE_KEY);
182
+ if (stored) {
183
+ const parsed = JSON.parse(stored) as Partial<IFilterState>;
184
+ filterStateVar({ ...DEFAULT_FILTERS, ...parsed });
185
+ }
186
+ } catch (error) {
187
+ const message = error instanceof Error ? error.message : "Unknown error";
188
+ console.error("Failed to load filter state:", message);
189
+ }
190
+ };
191
+
192
+ /**
193
+ * Save filter state to storage
194
+ */
195
+ const saveFilterState = async (): Promise<void> => {
196
+ try {
197
+ const current = filterStateVar();
198
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(current));
199
+ } catch (error) {
200
+ const message = error instanceof Error ? error.message : "Unknown error";
201
+ console.error("Failed to save filter state:", message);
202
+ }
203
+ };
204
+
205
+ /**
206
+ * Hook to set up persistence lifecycle
207
+ * Call once in a top-level component
208
+ */
209
+ export const useFilterStatePersistence = (): void => {
210
+ useEffect(() => {
211
+ // Load on mount
212
+ loadFilterState();
213
+
214
+ // Save on background/inactive
215
+ const handleAppStateChange = (nextAppState: AppStateStatus): void => {
216
+ if (nextAppState === "background" || nextAppState === "inactive") {
217
+ saveFilterState();
218
+ }
219
+ };
220
+
221
+ const subscription = AppState.addEventListener(
222
+ "change",
223
+ handleAppStateChange
224
+ );
225
+
226
+ return () => {
227
+ // Save on unmount as well
228
+ saveFilterState();
229
+ subscription.remove();
230
+ };
231
+ }, []);
232
+ };
233
+
234
+ /**
235
+ * Custom hook for consuming and updating filters
236
+ */
237
+ export const useFilterState = () => {
238
+ const filters = useReactiveVar(filterStateVar);
239
+
240
+ const setMinAge = useCallback((minAge: number) => {
241
+ filterStateVar({ ...filterStateVar(), minAge });
242
+ }, []);
243
+
244
+ const setMaxAge = useCallback((maxAge: number) => {
245
+ filterStateVar({ ...filterStateVar(), maxAge });
246
+ }, []);
247
+
248
+ const togglePosition = useCallback((position: string) => {
249
+ const current = filterStateVar();
250
+ const positions = current.positions.includes(position)
251
+ ? current.positions.filter(p => p !== position)
252
+ : [...current.positions, position];
253
+ filterStateVar({ ...current, positions });
254
+ }, []);
255
+
256
+ const resetFilters = useCallback(() => {
257
+ filterStateVar(DEFAULT_FILTERS);
258
+ }, []);
259
+
260
+ return {
261
+ filters,
262
+ setMinAge,
263
+ setMaxAge,
264
+ togglePosition,
265
+ resetFilters,
266
+ };
267
+ };
268
+ ```
269
+
270
+ ### When to Use
271
+
272
+ - Filter states and UI preferences
273
+ - Data that changes frequently during a session
274
+ - Non-critical state where occasional loss is acceptable
275
+
276
+ ### Trade-offs
277
+
278
+ - **Pro**: Fewer storage operations, better performance for frequently changing data
279
+ - **Con**: Risk of data loss if app crashes before backgrounding
280
+
281
+ ## Pattern 3: Debounced Persistence
282
+
283
+ Persist after a delay following the last change. Good for data that changes in bursts.
284
+
285
+ ### Implementation
286
+
287
+ ```typescript
288
+ import AsyncStorage from "@react-native-async-storage/async-storage";
289
+ import { makeVar, useReactiveVar } from "@apollo/client";
290
+ import { useCallback, useEffect, useRef } from "react";
291
+
292
+ // Types
293
+ interface ISearchHistory {
294
+ readonly queries: readonly string[];
295
+ readonly lastUpdated: string;
296
+ }
297
+
298
+ // Constants
299
+ const STORAGE_KEY = "@whatever:search-history";
300
+ const DEBOUNCE_MS = 1000;
301
+ const MAX_HISTORY_ITEMS = 20;
302
+ const DEFAULT_HISTORY: ISearchHistory = {
303
+ queries: [],
304
+ lastUpdated: new Date().toISOString(),
305
+ };
306
+
307
+ // Reactive Variable
308
+ export const searchHistoryVar = makeVar<ISearchHistory>(DEFAULT_HISTORY);
309
+
310
+ /**
311
+ * Custom hook with debounced persistence
312
+ */
313
+ export const useSearchHistory = () => {
314
+ const history = useReactiveVar(searchHistoryVar);
315
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
316
+
317
+ // Load on mount
318
+ useEffect(() => {
319
+ const loadHistory = async () => {
320
+ try {
321
+ const stored = await AsyncStorage.getItem(STORAGE_KEY);
322
+ if (stored) {
323
+ const parsed = JSON.parse(stored) as ISearchHistory;
324
+ searchHistoryVar(parsed);
325
+ }
326
+ } catch (error) {
327
+ console.error("Failed to load search history:", error);
328
+ }
329
+ };
330
+ loadHistory();
331
+ }, []);
332
+
333
+ // Debounced save function
334
+ const scheduleSave = useCallback(() => {
335
+ if (timeoutRef.current) {
336
+ clearTimeout(timeoutRef.current);
337
+ }
338
+
339
+ timeoutRef.current = setTimeout(async () => {
340
+ try {
341
+ const current = searchHistoryVar();
342
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(current));
343
+ } catch (error) {
344
+ console.error("Failed to save search history:", error);
345
+ }
346
+ }, DEBOUNCE_MS);
347
+ }, []);
348
+
349
+ // Cleanup on unmount
350
+ useEffect(() => {
351
+ return () => {
352
+ if (timeoutRef.current) {
353
+ clearTimeout(timeoutRef.current);
354
+ // Final save on unmount
355
+ AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(searchHistoryVar()));
356
+ }
357
+ };
358
+ }, []);
359
+
360
+ const addQuery = useCallback(
361
+ (query: string) => {
362
+ const current = searchHistoryVar();
363
+ const trimmedQuery = query.trim();
364
+
365
+ if (!trimmedQuery) return;
366
+
367
+ // Remove duplicate if exists, add to front
368
+ const filtered = current.queries.filter(q => q !== trimmedQuery);
369
+ const queries = [trimmedQuery, ...filtered].slice(0, MAX_HISTORY_ITEMS);
370
+
371
+ searchHistoryVar({
372
+ queries,
373
+ lastUpdated: new Date().toISOString(),
374
+ });
375
+
376
+ scheduleSave();
377
+ },
378
+ [scheduleSave]
379
+ );
380
+
381
+ const clearHistory = useCallback(() => {
382
+ searchHistoryVar(DEFAULT_HISTORY);
383
+ scheduleSave();
384
+ }, [scheduleSave]);
385
+
386
+ return {
387
+ queries: history.queries,
388
+ addQuery,
389
+ clearHistory,
390
+ };
391
+ };
392
+ ```
393
+
394
+ ### When to Use
395
+
396
+ - Search history or recent items
397
+ - Form draft auto-save
398
+ - Any data with burst updates
399
+
400
+ ### Trade-offs
401
+
402
+ - **Pro**: Optimal balance between persistence and performance
403
+ - **Con**: More complex implementation, potential for lost updates during debounce window
404
+
405
+ ## Pattern 4: Context Provider with Persistence
406
+
407
+ For state that needs to be available throughout the app with persistence.
408
+
409
+ ### Implementation
410
+
411
+ ```typescript
412
+ import AsyncStorage from "@react-native-async-storage/async-storage";
413
+ import React, {
414
+ createContext,
415
+ useContext,
416
+ useState,
417
+ useEffect,
418
+ useCallback,
419
+ useMemo,
420
+ ReactNode,
421
+ } from "react";
422
+
423
+ // Types
424
+ interface IFeatureFlags {
425
+ readonly darkModeEnabled: boolean;
426
+ readonly betaFeaturesEnabled: boolean;
427
+ readonly debugModeEnabled: boolean;
428
+ }
429
+
430
+ interface IFeatureFlagsContext extends IFeatureFlags {
431
+ readonly isLoaded: boolean;
432
+ readonly setDarkModeEnabled: (enabled: boolean) => void;
433
+ readonly setBetaFeaturesEnabled: (enabled: boolean) => void;
434
+ readonly setDebugModeEnabled: (enabled: boolean) => void;
435
+ }
436
+
437
+ // Constants
438
+ const STORAGE_KEY = "@whatever:feature-flags";
439
+ const DEFAULT_FLAGS: IFeatureFlags = {
440
+ darkModeEnabled: false,
441
+ betaFeaturesEnabled: false,
442
+ debugModeEnabled: false,
443
+ };
444
+
445
+ // Context
446
+ const FeatureFlagsContext = createContext<IFeatureFlagsContext | null>(null);
447
+
448
+ interface ProviderProps {
449
+ readonly children: ReactNode;
450
+ }
451
+
452
+ /**
453
+ * Feature flags provider with AsyncStorage persistence
454
+ */
455
+ export const FeatureFlagsProvider = ({ children }: ProviderProps) => {
456
+ const [flags, setFlags] = useState<IFeatureFlags>(DEFAULT_FLAGS);
457
+ const [isLoaded, setIsLoaded] = useState(false);
458
+
459
+ // Load from storage on mount
460
+ useEffect(() => {
461
+ const loadFlags = async () => {
462
+ try {
463
+ const stored = await AsyncStorage.getItem(STORAGE_KEY);
464
+ if (stored) {
465
+ const parsed = JSON.parse(stored) as Partial<IFeatureFlags>;
466
+ setFlags(prev => ({ ...prev, ...parsed }));
467
+ }
468
+ } catch (error) {
469
+ console.error("Failed to load feature flags:", error);
470
+ } finally {
471
+ setIsLoaded(true);
472
+ }
473
+ };
474
+ loadFlags();
475
+ }, []);
476
+
477
+ // Save helper
478
+ const saveFlags = useCallback(async (newFlags: IFeatureFlags) => {
479
+ try {
480
+ await AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newFlags));
481
+ } catch (error) {
482
+ console.error("Failed to save feature flags:", error);
483
+ }
484
+ }, []);
485
+
486
+ // Individual setters
487
+ const setDarkModeEnabled = useCallback(
488
+ (enabled: boolean) => {
489
+ setFlags(prev => {
490
+ const newFlags = { ...prev, darkModeEnabled: enabled };
491
+ saveFlags(newFlags);
492
+ return newFlags;
493
+ });
494
+ },
495
+ [saveFlags]
496
+ );
497
+
498
+ const setBetaFeaturesEnabled = useCallback(
499
+ (enabled: boolean) => {
500
+ setFlags(prev => {
501
+ const newFlags = { ...prev, betaFeaturesEnabled: enabled };
502
+ saveFlags(newFlags);
503
+ return newFlags;
504
+ });
505
+ },
506
+ [saveFlags]
507
+ );
508
+
509
+ const setDebugModeEnabled = useCallback(
510
+ (enabled: boolean) => {
511
+ setFlags(prev => {
512
+ const newFlags = { ...prev, debugModeEnabled: enabled };
513
+ saveFlags(newFlags);
514
+ return newFlags;
515
+ });
516
+ },
517
+ [saveFlags]
518
+ );
519
+
520
+ const contextValue = useMemo(
521
+ (): IFeatureFlagsContext => ({
522
+ ...flags,
523
+ isLoaded,
524
+ setDarkModeEnabled,
525
+ setBetaFeaturesEnabled,
526
+ setDebugModeEnabled,
527
+ }),
528
+ [flags, isLoaded, setDarkModeEnabled, setBetaFeaturesEnabled, setDebugModeEnabled]
529
+ );
530
+
531
+ return (
532
+ <FeatureFlagsContext.Provider value={contextValue}>
533
+ {children}
534
+ </FeatureFlagsContext.Provider>
535
+ );
536
+ };
537
+
538
+ /**
539
+ * Hook to consume feature flags
540
+ * @throws Error if used outside provider
541
+ */
542
+ export const useFeatureFlags = (): IFeatureFlagsContext => {
543
+ const context = useContext(FeatureFlagsContext);
544
+ if (!context) {
545
+ throw new Error("useFeatureFlags must be used within FeatureFlagsProvider");
546
+ }
547
+ return context;
548
+ };
549
+ ```
550
+
551
+ ### Usage
552
+
553
+ ```typescript
554
+ // In app root
555
+ export default function RootLayout() {
556
+ return (
557
+ <FeatureFlagsProvider>
558
+ <App />
559
+ </FeatureFlagsProvider>
560
+ );
561
+ }
562
+
563
+ // In any component
564
+ const SettingsScreen = () => {
565
+ const { darkModeEnabled, setDarkModeEnabled, isLoaded } = useFeatureFlags();
566
+
567
+ if (!isLoaded) {
568
+ return <LoadingSpinner />;
569
+ }
570
+
571
+ return (
572
+ <Switch
573
+ value={darkModeEnabled}
574
+ onValueChange={setDarkModeEnabled}
575
+ />
576
+ );
577
+ };
578
+ ```
579
+
580
+ ### When to Use
581
+
582
+ - Feature flags
583
+ - App-wide settings
584
+ - State that needs to be accessed before Apollo Client initializes
585
+
586
+ ### Trade-offs
587
+
588
+ - **Pro**: Works without Apollo Client, clear loading state
589
+ - **Con**: More boilerplate, separate from Apollo ecosystem
590
+
591
+ ## Choosing the Right Pattern
592
+
593
+ | Pattern | Best For | Performance | Reliability |
594
+ | ---------------- | ------------------ | ------------------- | ----------- |
595
+ | Immediate | Critical user data | Lower (more writes) | Highest |
596
+ | AppState-based | Frequent changes | Higher | Medium |
597
+ | Debounced | Burst updates | Highest | Medium |
598
+ | Context Provider | Non-Apollo state | Medium | High |
599
+
600
+ ## Combined Pattern Example
601
+
602
+ For complex features, combine patterns:
603
+
604
+ ```typescript
605
+ // stores/playerFilters.ts
606
+ import AsyncStorage from "@react-native-async-storage/async-storage";
607
+ import { makeVar, useReactiveVar } from "@apollo/client";
608
+ import { useCallback, useEffect, useRef } from "react";
609
+ import { AppState, AppStateStatus } from "react-native";
610
+
611
+ interface IPlayerFilters {
612
+ readonly minAge: number;
613
+ readonly maxAge: number;
614
+ readonly positions: readonly string[];
615
+ readonly searchQuery: string;
616
+ }
617
+
618
+ const STORAGE_KEY = "@whatever:player-filters";
619
+ const SEARCH_DEBOUNCE_MS = 300;
620
+ const DEFAULT_FILTERS: IPlayerFilters = {
621
+ minAge: 18,
622
+ maxAge: 45,
623
+ positions: [],
624
+ searchQuery: "",
625
+ };
626
+
627
+ export const playerFiltersVar = makeVar<IPlayerFilters>(DEFAULT_FILTERS);
628
+
629
+ export const usePlayerFilters = () => {
630
+ const filters = useReactiveVar(playerFiltersVar);
631
+ const searchTimeoutRef = useRef<NodeJS.Timeout | null>(null);
632
+
633
+ // Load on mount
634
+ useEffect(() => {
635
+ const load = async () => {
636
+ try {
637
+ const stored = await AsyncStorage.getItem(STORAGE_KEY);
638
+ if (stored) {
639
+ const parsed = JSON.parse(stored) as Partial<IPlayerFilters>;
640
+ playerFiltersVar({ ...DEFAULT_FILTERS, ...parsed });
641
+ }
642
+ } catch (error) {
643
+ console.error("Failed to load filters:", error);
644
+ }
645
+ };
646
+ load();
647
+ }, []);
648
+
649
+ // Save on background (for non-search fields)
650
+ useEffect(() => {
651
+ const handleAppState = (state: AppStateStatus) => {
652
+ if (state === "background") {
653
+ AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(playerFiltersVar()));
654
+ }
655
+ };
656
+
657
+ const sub = AppState.addEventListener("change", handleAppState);
658
+ return () => sub.remove();
659
+ }, []);
660
+
661
+ // Immediate save for critical changes (positions)
662
+ const togglePosition = useCallback((position: string) => {
663
+ const current = playerFiltersVar();
664
+ const positions = current.positions.includes(position)
665
+ ? current.positions.filter(p => p !== position)
666
+ : [...current.positions, position];
667
+
668
+ const newFilters = { ...current, positions };
669
+ playerFiltersVar(newFilters);
670
+
671
+ // Immediate persist for position changes
672
+ AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newFilters));
673
+ }, []);
674
+
675
+ // Debounced for search query
676
+ const setSearchQuery = useCallback((searchQuery: string) => {
677
+ playerFiltersVar({ ...playerFiltersVar(), searchQuery });
678
+
679
+ if (searchTimeoutRef.current) {
680
+ clearTimeout(searchTimeoutRef.current);
681
+ }
682
+
683
+ searchTimeoutRef.current = setTimeout(() => {
684
+ AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(playerFiltersVar()));
685
+ }, SEARCH_DEBOUNCE_MS);
686
+ }, []);
687
+
688
+ // Standard setters (saved on background)
689
+ const setMinAge = useCallback((minAge: number) => {
690
+ playerFiltersVar({ ...playerFiltersVar(), minAge });
691
+ }, []);
692
+
693
+ const setMaxAge = useCallback((maxAge: number) => {
694
+ playerFiltersVar({ ...playerFiltersVar(), maxAge });
695
+ }, []);
696
+
697
+ return {
698
+ filters,
699
+ setMinAge,
700
+ setMaxAge,
701
+ togglePosition,
702
+ setSearchQuery,
703
+ };
704
+ };
705
+ ```
706
+
707
+ This combined approach uses:
708
+
709
+ - **AppState persistence** for age filters (saved on background)
710
+ - **Immediate persistence** for position toggles (critical user selections)
711
+ - **Debounced persistence** for search query (changes with every keystroke)