@mediacubeco/expo-template-fsd 2.3.1 → 2.4.1

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 (333) hide show
  1. package/.claude/commands/git-commit.md +164 -0
  2. package/.claude/rules/architecture.md +38 -0
  3. package/.claude/rules/{prettier-eslint-typescript.mdc → prettier-eslint-typescript.md} +5 -10
  4. package/.claude/settings.json +2 -2
  5. package/.claude/skills/api/SKILL.md +362 -0
  6. package/.claude/skills/api/references/client/overview.md +106 -0
  7. package/.claude/skills/api/references/hooks/index.ts +3 -0
  8. package/.claude/skills/api/references/hooks/use-api-health.ts +25 -0
  9. package/.claude/skills/api/references/hooks/use-api-post.ts +46 -0
  10. package/.claude/skills/api/references/hooks/use-api-posts.ts +24 -0
  11. package/.claude/skills/api/references/mock/post.mock.ts +29 -0
  12. package/.claude/skills/{generate-api-model/references → api/references/model}/entities/common.ts +5 -2
  13. package/.claude/skills/api/references/model/resources/common.ts +9 -0
  14. package/.claude/skills/api/references/queries/health.ts +14 -0
  15. package/.claude/skills/api/references/queries/index.ts +2 -0
  16. package/.claude/skills/api/references/queries/post.ts +23 -0
  17. package/.claude/skills/api/references/repository/health/health.ts +11 -0
  18. package/.claude/skills/api/references/repository/health/health.types.ts +25 -0
  19. package/.claude/skills/api/references/repository/health/index.ts +2 -0
  20. package/.claude/skills/api/references/repository/post/index.ts +9 -0
  21. package/.claude/skills/api/references/repository/post/post.ts +34 -0
  22. package/.claude/skills/api/references/repository/post/post.types.ts +61 -0
  23. package/.claude/skills/code-style/SKILL.md +1025 -1
  24. package/.claude/skills/code-style/references/components/payout-speed-option/index.ts +2 -0
  25. package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.styles.ts +76 -0
  26. package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.tsx +58 -0
  27. package/.claude/skills/code-style/references/components/payout-speed-option/payout-speed-option.types.ts +22 -0
  28. package/.claude/skills/code-style/references/components/post/index.ts +2 -0
  29. package/.claude/skills/code-style/references/components/post/post.styles.ts +19 -0
  30. package/.claude/skills/code-style/references/components/post/post.tsx +21 -0
  31. package/.claude/skills/code-style/references/components/post/post.types.ts +9 -0
  32. package/.claude/skills/code-style/references/components/wrap/index.ts +2 -0
  33. package/.claude/skills/code-style/references/components/wrap/wrap.styles.ts +15 -0
  34. package/.claude/skills/code-style/references/components/wrap/wrap.tsx +31 -0
  35. package/.claude/skills/code-style/references/components/wrap/wrap.types.ts +10 -0
  36. package/.claude/skills/code-style/references/hooks/use-posts.ts +37 -0
  37. package/.claude/skills/code-style/references/pages/post/index.ts +4 -0
  38. package/.claude/skills/code-style/references/pages/post/post-details/index.ts +1 -0
  39. package/.claude/skills/code-style/references/pages/post/post-details/post-details.tsx +13 -0
  40. package/.claude/skills/code-style/references/pages/post/post-guide/index.ts +1 -0
  41. package/.claude/skills/code-style/references/pages/post/post-guide/post-guide.tsx +17 -0
  42. package/.claude/skills/code-style/references/pages/post/post-list/index.ts +1 -0
  43. package/.claude/skills/code-style/references/pages/post/post-list/post-list.styles.ts +17 -0
  44. package/.claude/skills/code-style/references/pages/post/post-list/post-list.tsx +25 -0
  45. package/.claude/skills/code-style/references/pages/post/post-share/index.ts +1 -0
  46. package/.claude/skills/code-style/references/pages/post/post-share/post-share.tsx +13 -0
  47. package/.claude/skills/code-style/references/routes/(stack)/_layout.tsx +18 -0
  48. package/.claude/skills/code-style/references/routes/(stack)/details.tsx +8 -0
  49. package/.claude/skills/code-style/references/routes/(stack)/guide.tsx +8 -0
  50. package/.claude/skills/code-style/references/routes/(stack)/index.tsx +8 -0
  51. package/.claude/skills/code-style/references/routes/(stack)/share.tsx +8 -0
  52. package/.claude/skills/code-style/references/routes/(tabs)/(post-details)/index.tsx +8 -0
  53. package/.claude/skills/code-style/references/routes/(tabs)/(post-list)/index.tsx +8 -0
  54. package/.claude/skills/code-style/references/routes/(tabs)/_layout.tsx +50 -0
  55. package/.claude/skills/code-style/references/routes/_app.tsx +20 -0
  56. package/.claude/skills/code-style/references/routes/guide.tsx +8 -0
  57. package/.claude/skills/code-style/references/routes/share.tsx +8 -0
  58. package/.claude/skills/code-style/references/store/use-post-draft-store.ts +62 -0
  59. package/.claude/skills/design-style/SKILL.md +320 -0
  60. package/.env +3 -1
  61. package/.env.example +3 -1
  62. package/CLAUDE.md +5 -11
  63. package/app.json +6 -3
  64. package/axios.d.ts +5 -0
  65. package/env.d.ts +3 -1
  66. package/package.json +33 -40
  67. package/patches/react-native-screens+4.25.2.patch +118 -0
  68. package/react-query.d.ts +13 -0
  69. package/src/app/(root)/(home)/_layout.tsx +2 -2
  70. package/src/app/(root)/(home)/index.tsx +2 -2
  71. package/src/app/(root)/(settings)/_layout.tsx +2 -2
  72. package/src/app/(root)/(settings)/index.tsx +2 -2
  73. package/src/app/(root)/_layout.tsx +35 -32
  74. package/src/pages/home/home-main.tsx +3 -7
  75. package/src/pages/settings/settings-main.tsx +3 -7
  76. package/src/shared/assets/fonts/index.ts +3 -3
  77. package/src/shared/assets/fonts/sf-pro-text-bold.ttf +0 -0
  78. package/src/shared/assets/fonts/sf-pro-text-regular.ttf +0 -0
  79. package/src/shared/assets/fonts/sf-pro-text-semibold.ttf +0 -0
  80. package/src/shared/assets/icons/back.tsx +21 -0
  81. package/src/shared/assets/icons/close.tsx +21 -0
  82. package/src/shared/assets/icons/common.ts +8 -0
  83. package/src/shared/assets/icons/default.tsx +44 -0
  84. package/src/shared/assets/icons/index.ts +14 -1
  85. package/src/shared/assets/icons/refresh.tsx +23 -0
  86. package/src/shared/assets/icons/share.tsx +38 -0
  87. package/src/shared/assets/icons/type.ts +7 -0
  88. package/src/shared/assets/translations/index.ts +4 -4
  89. package/src/shared/constants/config.ts +13 -0
  90. package/src/shared/constants/environment.ts +16 -4
  91. package/src/shared/constants/index.ts +1 -0
  92. package/src/shared/hooks/index.ts +1 -4
  93. package/src/shared/hooks/use-compose.ts +23 -0
  94. package/src/shared/lib/api/api.client.ts +157 -0
  95. package/src/shared/lib/api/api.provider.tsx +14 -36
  96. package/src/shared/lib/api/api.ts +1 -5
  97. package/src/shared/lib/api/api.types.ts +6 -3
  98. package/src/shared/lib/api/hooks/index.ts +1 -0
  99. package/src/shared/lib/api/index.ts +2 -3
  100. package/src/shared/lib/api/models/api-instance/api-instance.client.ts +50 -0
  101. package/src/shared/lib/api/models/api-instance/api-instance.ts +30 -82
  102. package/src/shared/lib/api/models/api-instance/api-instance.types.ts +16 -18
  103. package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +13 -20
  104. package/src/shared/lib/api/models/api-instance/index.ts +3 -0
  105. package/src/shared/lib/api/queries/index.ts +1 -0
  106. package/src/shared/lib/api/repositories/index.ts +1 -1
  107. package/src/shared/lib/localization/constants/index.ts +16 -5
  108. package/src/shared/lib/localization/index.ts +5 -1
  109. package/src/shared/lib/navigation/hooks/index.ts +8 -3
  110. package/src/shared/{hooks/use-breakpoints.ts → lib/navigation/hooks/use-navigation-breakpoints.ts} +2 -5
  111. package/src/shared/lib/navigation/hooks/use-navigation-dimensions.ts +12 -0
  112. package/src/shared/lib/navigation/hooks/use-navigation-options.tsx +175 -0
  113. package/src/shared/lib/navigation/hooks/{use-navigation.ts → use-navigation-route.ts} +2 -2
  114. package/src/shared/lib/navigation/hooks/use-navigation-safe-area.ts +61 -0
  115. package/src/shared/lib/navigation/hooks/use-navigation-tabs-native.ts +10 -0
  116. package/src/shared/lib/navigation/hooks/use-navigation-tabs.tsx +10 -0
  117. package/src/shared/lib/navigation/hooks/use-navigation-transition.ts +27 -0
  118. package/src/shared/lib/navigation/index.ts +11 -4
  119. package/src/shared/lib/navigation/navigation-tabs-native.context.tsx +5 -0
  120. package/src/shared/lib/navigation/navigation-tabs-native.provider.tsx +17 -0
  121. package/src/shared/lib/navigation/navigation-tabs.context.tsx +10 -0
  122. package/src/shared/lib/navigation/navigation-tabs.provider.tsx +22 -0
  123. package/src/shared/lib/navigation/navigation.provider.tsx +6 -1
  124. package/src/shared/lib/storage/index.ts +1 -1
  125. package/src/shared/lib/storage/storage.provider.tsx +4 -0
  126. package/src/shared/lib/storage/storage.ts +29 -25
  127. package/src/shared/lib/theme/constants/animations.ts +26 -0
  128. package/src/shared/lib/theme/constants/colors.ts +336 -43
  129. package/src/shared/lib/theme/constants/delays.ts +1 -1
  130. package/src/shared/lib/theme/constants/durations.ts +1 -1
  131. package/src/shared/lib/theme/constants/fonts.ts +2 -1
  132. package/src/shared/lib/theme/constants/index.ts +42 -20
  133. package/src/shared/lib/theme/constants/shadows.ts +7 -25
  134. package/src/shared/lib/theme/constants/sizes.ts +11 -2
  135. package/src/shared/lib/theme/constants/transparencies.ts +1 -1
  136. package/src/shared/lib/theme/hooks/use-theme-setup.ts +3 -1
  137. package/src/shared/lib/theme/index.ts +10 -1
  138. package/src/shared/lib/theme/theme.provider.tsx +45 -19
  139. package/src/shared/lib/theme/theme.scope.tsx +43 -0
  140. package/src/shared/lib/theme/theme.types.ts +15 -3
  141. package/src/shared/lib/theme/utils/color.ts +81 -0
  142. package/src/shared/lib/theme/utils/index.ts +2 -0
  143. package/src/shared/lib/theme/utils/snapshot.ts +17 -0
  144. package/src/shared/lib/theme/utils/storage.ts +5 -2
  145. package/src/shared/services/browser/browser.ts +66 -0
  146. package/src/shared/services/browser/index.ts +1 -0
  147. package/src/shared/services/cookies/cookies.ts +73 -0
  148. package/src/shared/services/cookies/index.ts +1 -0
  149. package/src/shared/services/index.ts +5 -0
  150. package/src/shared/services/linking/index.ts +1 -0
  151. package/src/shared/services/linking/linking.ts +22 -0
  152. package/src/shared/services/share/index.ts +1 -0
  153. package/src/shared/services/share/share.ts +28 -0
  154. package/src/shared/services/universal-links/index.ts +104 -0
  155. package/src/shared/services/universal-links/universal-links.ts +25 -0
  156. package/src/shared/services/universal-links/universal-links.types.ts +5 -0
  157. package/src/shared/ui/atoms/blur/blur.tsx +9 -4
  158. package/src/shared/ui/atoms/blur/blur.types.ts +1 -1
  159. package/src/shared/ui/atoms/blur-progress/blur-progress.styles.ts +16 -0
  160. package/src/shared/ui/atoms/blur-progress/blur-progress.tsx +30 -0
  161. package/src/shared/ui/atoms/blur-progress/blur-progress.types.tsx +5 -0
  162. package/src/shared/ui/atoms/blur-progress/index.ts +1 -0
  163. package/src/shared/ui/atoms/glass/glass.tsx +13 -5
  164. package/src/shared/ui/atoms/glass/glass.types.ts +1 -1
  165. package/src/shared/ui/atoms/icon/icon.styles.ts +82 -0
  166. package/src/shared/ui/atoms/icon/icon.tsx +21 -0
  167. package/src/shared/ui/atoms/icon/icon.types.ts +23 -0
  168. package/src/shared/ui/atoms/icon/index.ts +2 -0
  169. package/src/shared/ui/atoms/index.ts +7 -0
  170. package/src/shared/ui/atoms/skeleton/index.ts +2 -0
  171. package/src/shared/ui/atoms/skeleton/skeleton.styles.ts +69 -0
  172. package/src/shared/ui/atoms/skeleton/skeleton.tsx +26 -0
  173. package/src/shared/ui/atoms/skeleton/skeleton.types.ts +9 -0
  174. package/src/shared/ui/atoms/spinner/index.ts +2 -0
  175. package/src/shared/ui/atoms/spinner/spinner.styles.ts +102 -0
  176. package/src/shared/ui/atoms/spinner/spinner.tsx +48 -0
  177. package/src/shared/ui/atoms/spinner/spinner.types.ts +25 -0
  178. package/src/shared/ui/atoms/text/index.ts +2 -0
  179. package/src/shared/ui/atoms/text/text.styles.ts +186 -0
  180. package/src/shared/ui/atoms/text/text.tsx +37 -0
  181. package/src/shared/ui/atoms/text/text.types.ts +35 -0
  182. package/src/shared/ui/atoms/touch/components/haptics/haptics.styles.ts +21 -0
  183. package/src/shared/ui/atoms/touch/components/haptics/haptics.tsx +28 -0
  184. package/src/shared/ui/atoms/touch/components/haptics/haptics.types.ts +17 -0
  185. package/src/shared/ui/atoms/touch/components/haptics/index.ts +2 -0
  186. package/src/shared/ui/atoms/touch/components/index.ts +3 -0
  187. package/src/shared/ui/atoms/touch/components/ripple/index.ts +2 -0
  188. package/src/shared/ui/atoms/touch/components/ripple/ripple.styles.ts +78 -0
  189. package/src/shared/ui/atoms/touch/components/ripple/ripple.tsx +29 -0
  190. package/src/shared/ui/atoms/touch/components/ripple/ripple.types.ts +17 -0
  191. package/src/shared/ui/atoms/touch/components/spring/index.ts +2 -0
  192. package/src/shared/ui/atoms/touch/components/spring/spring.styles.ts +31 -0
  193. package/src/shared/ui/atoms/touch/components/spring/spring.tsx +28 -0
  194. package/src/shared/ui/atoms/touch/components/spring/spring.types.ts +17 -0
  195. package/src/shared/ui/atoms/touch/index.ts +2 -0
  196. package/src/shared/ui/atoms/touch/touch.styles.ts +14 -0
  197. package/src/shared/ui/atoms/touch/touch.tsx +137 -0
  198. package/src/shared/ui/atoms/touch/touch.types.ts +27 -0
  199. package/src/shared/ui/atoms/visibility/index.ts +2 -0
  200. package/src/shared/ui/atoms/visibility/visibility.styles.ts +85 -0
  201. package/src/shared/ui/atoms/visibility/visibility.tsx +61 -0
  202. package/src/shared/ui/atoms/visibility/visibility.types.ts +34 -0
  203. package/src/shared/ui/molecules/keyboard-area/keyboard-area.styles.ts +11 -9
  204. package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +3 -6
  205. package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +3 -5
  206. package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +1 -1
  207. package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.styles.ts +41 -0
  208. package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.tsx +12 -0
  209. package/src/shared/ui/molecules/slider/components/indicator/components/dot/dot.types.ts +13 -0
  210. package/src/shared/ui/molecules/slider/components/indicator/components/dot/index.ts +2 -0
  211. package/src/shared/ui/molecules/slider/components/indicator/index.ts +2 -0
  212. package/src/shared/ui/molecules/slider/components/indicator/indicator.styles.ts +20 -0
  213. package/src/shared/ui/molecules/slider/components/indicator/indicator.tsx +22 -0
  214. package/src/shared/ui/molecules/slider/components/indicator/indicator.types.ts +10 -0
  215. package/src/shared/ui/molecules/slider/index.ts +3 -0
  216. package/src/shared/ui/molecules/slider/slider.styles.ts +68 -0
  217. package/src/shared/ui/molecules/slider/slider.tsx +64 -0
  218. package/src/shared/ui/molecules/slider/slider.types.ts +28 -0
  219. package/src/shared/ui/organisms/bar/bar.styles.ts +100 -0
  220. package/src/shared/ui/organisms/bar/bar.tsx +89 -0
  221. package/src/shared/ui/organisms/bar/bar.types.ts +49 -0
  222. package/src/shared/ui/organisms/bar/index.ts +2 -0
  223. package/src/shared/ui/organisms/bar-tab/bar-tab.tsx +9 -0
  224. package/src/shared/ui/organisms/bar-tab/bar-tab.types.ts +5 -0
  225. package/src/shared/ui/organisms/bar-tab/components/index.ts +1 -0
  226. package/src/shared/ui/organisms/bar-tab/components/tab/index.ts +2 -0
  227. package/src/shared/ui/organisms/bar-tab/components/tab/tab.styles.ts +21 -0
  228. package/src/shared/ui/organisms/bar-tab/components/tab/tab.tsx +18 -0
  229. package/src/shared/ui/organisms/bar-tab/components/tab/tab.types.ts +5 -0
  230. package/src/shared/ui/organisms/bar-tab/index.ts +2 -0
  231. package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.styles.ts +22 -0
  232. package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.tsx +66 -0
  233. package/src/shared/ui/organisms/bar-tab-navigation/bar-tab-navigation.types.ts +5 -0
  234. package/src/shared/ui/organisms/bar-tab-navigation/components/index.ts +1 -0
  235. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/index.ts +2 -0
  236. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.styles.ts +56 -0
  237. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.tsx +30 -0
  238. package/src/shared/ui/organisms/bar-tab-navigation/components/tab-navigation/tab-navigation.types.ts +7 -0
  239. package/src/shared/ui/organisms/bar-tab-navigation/index.ts +2 -0
  240. package/src/shared/ui/organisms/index.ts +4 -1
  241. package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.styles.ts +41 -0
  242. package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.tsx +12 -0
  243. package/src/shared/ui/organisms/slider/components/indicator/components/dot/dot.types.ts +13 -0
  244. package/src/shared/ui/organisms/slider/components/indicator/components/dot/index.ts +2 -0
  245. package/src/shared/ui/organisms/slider/components/indicator/index.ts +2 -0
  246. package/src/shared/ui/organisms/slider/components/indicator/indicator.styles.ts +20 -0
  247. package/src/shared/ui/organisms/slider/components/indicator/indicator.tsx +22 -0
  248. package/src/shared/ui/organisms/slider/components/indicator/indicator.types.ts +10 -0
  249. package/src/shared/ui/organisms/slider/index.ts +3 -0
  250. package/src/shared/ui/organisms/slider/slider.styles.ts +68 -0
  251. package/src/shared/ui/organisms/slider/slider.tsx +64 -0
  252. package/src/shared/ui/organisms/slider/slider.types.ts +28 -0
  253. package/src/shared/ui/templates/index.ts +3 -0
  254. package/src/shared/ui/templates/modal-container/index.ts +13 -0
  255. package/src/shared/ui/templates/modal-container/modal-container-list.tsx +45 -0
  256. package/src/shared/ui/templates/modal-container/modal-container-scroll.tsx +66 -0
  257. package/src/shared/ui/templates/modal-container/modal-container-section-list.tsx +46 -0
  258. package/src/shared/ui/templates/modal-container/modal-container.hooks.tsx +172 -0
  259. package/src/shared/ui/templates/modal-container/modal-container.styles.ts +121 -0
  260. package/src/shared/ui/templates/modal-container/modal-container.types.ts +69 -0
  261. package/src/shared/ui/templates/screen-container/index.ts +1 -1
  262. package/src/shared/ui/templates/screen-container/screen-container-list.tsx +28 -10
  263. package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +28 -10
  264. package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +29 -11
  265. package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +79 -8
  266. package/src/shared/ui/templates/screen-container/screen-container.styles.ts +105 -15
  267. package/src/shared/ui/templates/screen-container/screen-container.types.tsx +34 -7
  268. package/src/shared/ui/templates/screen-header/components/index.ts +6 -0
  269. package/src/shared/ui/templates/screen-header/components/screen-header-description/index.ts +2 -0
  270. package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.tsx +15 -0
  271. package/src/shared/ui/templates/screen-header/components/screen-header-description/screen-header-description.types.ts +11 -0
  272. package/src/shared/ui/templates/screen-header/components/screen-header-icon/index.ts +2 -0
  273. package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.styles.ts +44 -0
  274. package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.tsx +65 -0
  275. package/src/shared/ui/templates/screen-header/components/screen-header-icon/screen-header-icon.types.ts +14 -0
  276. package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/index.ts +2 -0
  277. package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.tsx +17 -0
  278. package/src/shared/ui/templates/screen-header/components/screen-header-icon-back/screen-header-icon-back.types.ts +6 -0
  279. package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/index.ts +2 -0
  280. package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.tsx +27 -0
  281. package/src/shared/ui/templates/screen-header/components/screen-header-icon-close/screen-header-icon-close.types.ts +10 -0
  282. package/src/shared/ui/templates/screen-header/components/screen-header-info/index.ts +2 -0
  283. package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.tsx +15 -0
  284. package/src/shared/ui/templates/screen-header/components/screen-header-info/screen-header-info.types.ts +10 -0
  285. package/src/shared/ui/templates/screen-header/components/screen-header-title/index.ts +2 -0
  286. package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.tsx +19 -0
  287. package/src/shared/ui/templates/screen-header/components/screen-header-title/screen-header-title.types.ts +13 -0
  288. package/src/shared/ui/templates/screen-header/index.ts +3 -0
  289. package/src/shared/ui/templates/screen-header/screen-header.styles.ts +49 -0
  290. package/src/shared/ui/templates/screen-header/screen-header.tsx +26 -0
  291. package/src/shared/ui/templates/screen-header/screen-header.types.ts +15 -0
  292. package/src/shared/ui/templates/web-container/index.ts +2 -0
  293. package/src/shared/ui/templates/web-container/web-container.styles.ts +34 -0
  294. package/src/shared/ui/templates/web-container/web-container.tsx +172 -0
  295. package/src/shared/ui/templates/web-container/web-container.types.ts +21 -0
  296. package/.claude/rules/app-routing.mdc +0 -47
  297. package/.claude/rules/exports-at-end.mdc +0 -55
  298. package/.claude/rules/kebab-case-naming.mdc +0 -29
  299. package/.claude/rules/mediacubeco-packages.mdc +0 -56
  300. package/.claude/skills/generate-api-model/SKILL.md +0 -230
  301. package/.claude/skills/generate-api-model/references/resources/common.ts +0 -8
  302. package/.claude/skills/generate-api-model-mock/SKILL.md +0 -56
  303. package/.claude/skills/git-commit/SKILL.md +0 -139
  304. package/.gitignore +0 -34
  305. package/src/shared/assets/icons/like.tsx +0 -17
  306. package/src/shared/hooks/use-animated.ts +0 -200
  307. package/src/shared/hooks/use-mixins.ts +0 -36
  308. package/src/shared/hooks/use-safe-area.ts +0 -75
  309. package/src/shared/lib/api/repositories/auth/auth.ts +0 -23
  310. package/src/shared/lib/api/repositories/auth/auth.types.ts +0 -22
  311. package/src/shared/lib/api/repositories/auth/index.ts +0 -1
  312. package/src/shared/lib/navigation/hooks/use-navigation-native-tabs.ts +0 -10
  313. package/src/shared/lib/navigation/hooks/use-navigation-options.ts +0 -123
  314. package/src/shared/lib/navigation/navigation-native-tabs.context.tsx +0 -5
  315. package/src/shared/lib/navigation/navigation-native-tabs.provider.tsx +0 -12
  316. package/src/shared/lib/theme/constants/borders.ts +0 -8
  317. package/src/shared/lib/theme/constants/weights.ts +0 -13
  318. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/index.ts +0 -0
  319. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/index.ts +0 -0
  320. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post-comment.ts +0 -0
  321. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/post/post.ts +0 -0
  322. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/index.ts +0 -0
  323. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-settings.ts +0 -0
  324. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user-verification.ts +0 -0
  325. /package/.claude/skills/{generate-api-model/references → api/references/model}/entities/user/user.ts +0 -0
  326. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/index.ts +0 -0
  327. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/index.ts +0 -0
  328. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post-comment.ts +0 -0
  329. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/post/post.ts +0 -0
  330. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/index.ts +0 -0
  331. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-settings.ts +0 -0
  332. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user-verification.ts +0 -0
  333. /package/.claude/skills/{generate-api-model/references → api/references/model}/resources/user/user.ts +0 -0
@@ -0,0 +1,164 @@
1
+ ---
2
+ allowed-tools: "Bash(git branch --show-current)"
3
+ ---
4
+
5
+ # Git Commit
6
+
7
+ Creates git commits with proper messages and decomposes staged/unstaged changes into logical atomic commits.
8
+
9
+ ## Step 1: Understand the branch
10
+
11
+ Current branch name:
12
+
13
+ !`git branch --show-current`
14
+
15
+ Derive the **prefix** from the branch name above in two steps:
16
+
17
+ 1. **Strip the workflow prefix** — drop any leading `feature/`, `fix/`, `chore/`, etc. by taking everything after the last `/`. E.g. `feature/CU-869dgtyz4` → `CU-869dgtyz4`.
18
+ 2. **Strip the author postfix** — the task identifier is `CU-` plus the single token right after it (e.g. `CU-869dgtyz4`). Drop any further `-<segment>` that follows it — that's a developer/author suffix, not part of the ID. E.g. `CU-869dgtyz4-sulumov` → `CU-869dgtyz4`, and `feature/CU-869dgtyz4-sulumov` → `CU-869dgtyz4`.
19
+
20
+ Use that identifier as `{prefix}` everywhere below. If the branch has no task identifier (e.g., `main`, `develop`), commit without a prefix.
21
+
22
+ ## Step 2: Analyze all changes
23
+
24
+ Run these together to get the full picture:
25
+
26
+ ```bash
27
+ git status
28
+ git diff HEAD
29
+ git diff --cached
30
+ ```
31
+
32
+ Read the actual diff carefully — don't just look at filenames. You need to understand _what_ each change does, not just _where_ it is, to group things correctly.
33
+
34
+ ## Step 3: Decompose into logical commits
35
+
36
+ This is the most important step. Think like a reviewer who will read this history in six months: they want the **fewest commits that still tell the story clearly** — each one a coherent, self-contained change they could review or revert on its own.
37
+
38
+ **Each commit should be one coherent unit of work — a whole change that stands on its own.** Default to **grouping, not splitting**: a feature and everything that exists only to serve it (its types, styles, hook, shared helper — across whatever FSD layers it touches) is normally **one** commit, not one per file or per layer.
39
+
40
+ Good decomposition principles:
41
+
42
+ - **Group by unit of work, not by file or by layer** — a feature that touches `shared` + `entities` + `features` + `pages` is usually **one** commit when those changes only exist to make that one feature work. Crossing an FSD layer boundary is _not_ by itself a reason to split.
43
+ - **Split only when the pieces are genuinely independent** — two changes deserve separate commits when each stands on its own: it could be reviewed, reverted, or shipped without the other. If pulling them apart leaves either half meaningless or broken, keep them together.
44
+ - **Truly reusable infrastructure can stand alone** — a shared component/util that multiple _unrelated_ consumers will use is worth its own commit. But a "shared" piece written specifically for one feature in this same batch goes _with_ that feature, not on its own.
45
+ - **Config/tooling separate from features** — `eslint`, `tsconfig`, `package.json`, `.env` changes are their own commit, unless they were added specifically to enable a single feature that's in the same batch.
46
+ - **Refactoring separate from new behavior** — a real refactor (extracting, renaming, restructuring) is its own commit. Don't over-apply this to trivial incidental tidy-ups that ride along with the feature.
47
+ - **Unrelated changes never share a commit** — separate concerns that merely happened in the same session go in separate commits.
48
+
49
+ As a rough gauge: most batches decompose into **1–4 commits**. If you're planning many more, you're almost certainly splitting by file or by layer instead of by unit of work — regroup. A big feature can legitimately be a big commit; size alone is never the reason to split.
50
+
51
+ Common anti-patterns to avoid:
52
+
53
+ - **One giant "various fixes" commit** — decompose it into its distinct units of work.
54
+ - **Death by a thousand commits** — splitting one coherent change into a commit per file, per FSD layer, or per type definition. If a reviewer must read three commits to understand one feature, you split too far.
55
+ - **Splitting one logical unit across commits** — e.g. a component in one commit and its required types/styles/hook in another.
56
+ - **Committing unrelated files together** just because they were modified in the same session.
57
+
58
+ After analyzing, produce a decomposition plan like this (show it mentally, then execute without asking for confirmation unless the decomposition is genuinely ambiguous):
59
+
60
+ ```
61
+ Commit 1: {prefix} - add user API layer (entity, resource, repository)
62
+ → src/shared/lib/api/entities/user.ts
63
+ → src/shared/lib/api/resources/user.ts
64
+ → src/shared/lib/api/models/user-repository.ts
65
+ → src/shared/lib/api/api.ts (registration)
66
+
67
+ Commit 2: {prefix} - add profile screen with user data fetching
68
+ → src/pages/profile/
69
+ → src/features/profile/
70
+ ```
71
+
72
+ Two commits, not five: the whole API layer is one unit of work (don't split entity from resource from repository), and the screen that consumes it is another. Had the API layer been built solely for this one screen and used nowhere else, folding both into a single commit would be fine too.
73
+
74
+ The plan above lists subjects only. When you execute each commit (Step 4), also write its Russian body — see [Commit message format](#commit-message-format).
75
+
76
+ ## Step 4: Stage and commit each group
77
+
78
+ For each commit in the plan, stage exactly the relevant files and commit:
79
+
80
+ ```bash
81
+ # Stage specific files (never use git add . or git add -A)
82
+ git add <specific files>
83
+
84
+ # Commit: English subject as the first -m, Russian body as the second -m
85
+ git commit -m "{prefix} - add UserEntity and UserResource types" -m "- Добавлены сущность UserEntity и ресурс UserResource
86
+ - Поля приведены к camelCase с безопасными дефолтами"
87
+ ```
88
+
89
+ Repeat for each commit in the plan.
90
+
91
+ If a single file contains changes that belong to different logical commits (mixed concerns in one file), use `git add -p` to stage only the relevant hunks:
92
+
93
+ ```bash
94
+ git add -p src/shared/lib/api/api.ts
95
+ ```
96
+
97
+ ## Commit message format
98
+
99
+ Every commit has two parts: a one-line **subject** in English, and a multi-line **body** in Russian.
100
+
101
+ ### Subject — English
102
+
103
+ ```
104
+ {prefix} - short imperative description
105
+ ```
106
+
107
+ Rules:
108
+
109
+ - Start with `{prefix}` — the identifier from Step 1
110
+ - Follow with `-` then the message
111
+ - Use imperative mood: "add", "fix", "remove", "update", "extract" — not "added", "adding", "fixes"
112
+ - Be specific: "add UserEntity with profile fields" beats "add user types"
113
+ - Keep it under 72 characters total
114
+ - No period at the end
115
+
116
+ | Situation | Subject |
117
+ | -------------------- | ----------------------------------------------------- |
118
+ | New API types | `{prefix} - add OrderResource and OrderEntity` |
119
+ | New screen | `{prefix} - add checkout screen with order summary` |
120
+ | Bug fix | `{prefix} - fix token refresh on 401 response` |
121
+ | Config change | `{prefix} - add react-query persistence config` |
122
+ | Refactor | `{prefix} - extract AuthHeader into shared component` |
123
+ | Multiple small fixes | `{prefix} - fix navigation types and screen params` |
124
+
125
+ ### Body — Russian
126
+
127
+ The body explains, **in Russian**, what this commit actually changed — enough that reading the commit alone makes the change clear, without opening the diff. The subject stays English; only the body is Russian.
128
+
129
+ Write it as a short bullet list, one bullet per meaningful change in **this** commit:
130
+
131
+ - Each bullet is a concrete thing that was done or reworked, in past tense ("Добавлен…", "Вынесен…", "Исправлен…", "Переименован…").
132
+ - Group by what changed, not file-by-file.
133
+ - По делу: skip filler, don't restate the subject, don't narrate the obvious ("обновлён код", "внесены правки").
134
+ - 1–5 bullets for a typical commit. A trivial commit may be a single bullet — don't pad it; a large one shouldn't turn into an essay.
135
+
136
+ **Full example:**
137
+
138
+ ```
139
+ CU-869dgtyz4 - add user API layer
140
+
141
+ - Добавлены сущность UserEntity и ресурс UserResource с полями профиля
142
+ - Поля приведены к camelCase с безопасными дефолтами
143
+ - UserRepository зарегистрирован в Api singleton
144
+ ```
145
+
146
+ ## Step 5: Verify
147
+
148
+ After all commits are done:
149
+
150
+ ```bash
151
+ git log --oneline -10
152
+ ```
153
+
154
+ Each commit should read cleanly as a standalone unit of work. If two adjacent commits feel like they should be one, or one commit feels like it should be two — that's worth noting but not worth amending if already done.
155
+
156
+ ## Edge cases
157
+
158
+ **Nothing staged, nothing to commit** — say so and stop. Don't create empty commits.
159
+
160
+ **Only whitespace / formatting changes** — still a valid commit: subject `{prefix} - format auth module`, body e.g. `- Прогнан prettier по модулю auth`.
161
+
162
+ **Single logical change across many files** — fine, that's one commit. Stage all the files together.
163
+
164
+ **Uncertain decomposition** — if you genuinely cannot tell whether two changes are the same logical unit, keep them together. Atomic is better than artificially split.
@@ -0,0 +1,38 @@
1
+ # Architecture guardrails: `src/app` and `src/shared`
2
+
3
+ `src/app` and `src/shared` are the two most sensitive layers of the codebase —
4
+ every screen and every slice depends on them. They are kept in full cleanliness,
5
+ and changes there are never routine.
6
+
7
+ ## Be extremely careful — and ask first
8
+
9
+ Before **adding or changing anything** in `src/app` or `src/shared` (a new
10
+ shared component, a new dependency, a new provider, a new util, a change to an
11
+ existing shared API), **always ask the user first**. Describe what you want to
12
+ add, where, and why it belongs on that layer. This holds even when the change
13
+ seems obviously useful — the cost of a wrong abstraction on these layers is
14
+ paid by the whole app.
15
+
16
+ - `src/app` stays maximally empty: route wiring only (see the code-style
17
+ skill, section "Routes"). No business logic, no state, no extra
18
+ dependencies.
19
+ - `src/shared` holds only genuinely reusable, business-agnostic code. A
20
+ component or helper needed by one feature belongs to that feature's slice,
21
+ not to `src/shared`.
22
+
23
+ ## Watch for degradation — and speak up
24
+
25
+ Whenever you notice these layers drifting in a bad direction — while editing,
26
+ reviewing, or just reading nearby code — **point it out proactively and
27
+ propose a fix**, even if it is outside the current task:
28
+
29
+ - logic or new dependencies accumulating in `src/app` (providers gaining
30
+ business code, route files growing beyond thin wrappers, boot logic
31
+ spreading);
32
+ - unclear or single-consumer components appearing in `src/shared/ui`,
33
+ duplicated helpers in `src/shared`, business knowledge leaking into the
34
+ shared layer.
35
+
36
+ Flag the problem, explain why it violates the layer's contract, and suggest
37
+ where the code should live instead. Keeping these two layers clean matters
38
+ more than the convenience of any single change.
@@ -1,24 +1,19 @@
1
- ---
2
- description: All code must satisfy Prettier, ESLint, and TypeScript for this project
3
- alwaysApply: true
4
- ---
5
-
6
1
  # Prettier, ESLint, and TypeScript
7
2
 
8
3
  Every change you produce must align with the project toolchain: **Prettier** (`prettier.config.js`), **ESLint** (`eslint.config.js`), and **TypeScript** (`tsconfig.json` and extended configs).
9
4
 
10
5
  ## Expectations
11
6
 
12
- - **Prettier**: formatting, line breaks, and import/style choices must match what Prettier would produce for this repo (run `npm format` or format-on-save with the same config).
7
+ - **Prettier**: formatting, line breaks, and import/style choices must match what Prettier would produce for this repo (run `npm run format` or format-on-save with the same config).
13
8
  - **ESLint**: no new violations of project rules; fix issues instead of widening ignores. Avoid blanket `eslint-disable` unless there is a documented, narrow reason.
14
- - **TypeScript**: valid types, no errors under `npm type` (`tsc`). Do not paper over problems with `any`, unchecked casts, or `@ts-expect-error` / `@ts-ignore` unless unavoidable and explained in place.
9
+ - **TypeScript**: valid types, no errors under `npm run type` (`tsc`). Do not paper over problems with `any`, unchecked casts, or `@ts-expect-error` / `@ts-ignore` unless unavoidable and explained in place.
15
10
 
16
11
  ## Verification
17
12
 
18
13
  After substantive edits, prefer that the tree would pass:
19
14
 
20
- - `npm format`
21
- - `npm lint`
22
- - `npm type`
15
+ - `npm run format`
16
+ - `npm run lint`
17
+ - `npm run type`
23
18
 
24
19
  If a constraint from the configs conflicts with another instruction, follow the stricter requirement or resolve the conflict explicitly in code without disabling tools by default.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "enabledPlugins": {
3
- "gitlab@claude-plugins-official": true,
3
+ "gitlab@claude-plugins-official": false,
4
4
 
5
- "superpowers@claude-plugins-official": true,
5
+ "superpowers@claude-plugins-official": false,
6
6
 
7
7
  "expo@claude-plugins-official": true,
8
8
  "code-review@claude-plugins-official": true,
@@ -0,0 +1,362 @@
1
+ ---
2
+ name: api
3
+ description: >-
4
+ Generates and edits the project's shared API layer end to end — Resource types
5
+ and Entity classes from backend JSON, repository classes over ApiInstance,
6
+ TanStack Query option factories (queries/), useQuery/useMutation hooks, and
7
+ mock fixtures. Use this skill whenever the user works on the API/data layer:
8
+ "add an API for ...", "model this backend JSON", "create an entity/resource",
9
+ "add a repository", "generate query hooks", "connect this endpoint to React",
10
+ "add a mutation", "make mocks for ...", or adds/edits anything under
11
+ resources/, entities/, repositories/, queries/, hooks/. Also use when wiring a
12
+ new domain into the shared api lib or when reviewing API-layer code for
13
+ correctness. Prefer this skill over hand-rolling fetch calls, query keys, or
14
+ TanStack Query wiring.
15
+ ---
16
+
17
+ # API layer
18
+
19
+ This skill generates the project's shared API layer so every domain looks like
20
+ one hand wrote it. General conventions (naming, exports at end of file,
21
+ async/await, file casing, barrels) come from the **code-style** skill — follow it
22
+ for everything; this skill only adds the API-specific architecture on top.
23
+
24
+ ## The architecture in one picture
25
+
26
+ Data flows through fixed layers. Get the **direction of transformation** right —
27
+ it is the thing most often done wrong:
28
+
29
+ ```
30
+ backend JSON → Resource (wire shape, optional fields, snake_case)
31
+ ↓ consumed by
32
+ Entity (class, camelCase, safe defaults) ← lives in the HOOK's select
33
+ repository → returns the RAW server response, unchanged ( e.g. { data?: Resource[] } — plain, snake_case )
34
+ queries/ → queryOptions + query-key factory ( key + queryFn + staleTime + meta )
35
+ hooks/ → useQuery({ ...queries.x(), select }) ← select SHAPES { list }/{ item } + maps → entities
36
+ ```
37
+
38
+ **Why the cache holds raw plain responses and entities live in `select`:** the
39
+ cache stores exactly what the repository returns. TanStack Query's structural
40
+ sharing preserves references only for plain objects/arrays — class instances break
41
+ it, so caching entities causes a new reference on every refetch and re-renders
42
+ everything. Plain responses also serialize cleanly for persistence. `select` is
43
+ memoized by TanStack Query and only re-runs when the raw data actually changes, so
44
+ the consumer-facing shape (`{ list }`/`{ item }`) and the entity instances are
45
+ derived exactly when needed. **Never** return entities or a reshaped `{ list }`/
46
+ `{ item }` from a repository, and never build entities in `queryFn` — the
47
+ repository is a thin pass-through of the wire payload; all shaping happens in
48
+ `select`.
49
+
50
+ Read `references/client/overview.md` once — it explains the shared machine
51
+ (transport options, centralized `onError`, typed `meta`, persistence, retry) that
52
+ every domain plugs into.
53
+
54
+ ## Generation order
55
+
56
+ A full domain is built bottom-up. Each step has authoritative reference files —
57
+ read them before writing, they are the source of truth for shape and style:
58
+
59
+ | Step | Produces | References |
60
+ | ---- | -------- | ---------- |
61
+ | 1. Model | `resources/<domain>/`, `entities/<domain>/` | `references/model/` |
62
+ | 2. Repository | `repositories/<domain>/` (`.ts`, `.types.ts`, `index.ts`) | `references/repository/post/` |
63
+ | 3. Queries | `queries/<domain>.ts` | `references/queries/post.ts` |
64
+ | 4. Hooks | `hooks/use-api-<domain>(s).ts`, mutation hooks | `references/hooks/` |
65
+ | 5. Mock (on request) | fixtures next to the domain or in the project's mocks dir | `references/mock/post.mock.ts` |
66
+
67
+ Then wire the domain into shared files (see "Wiring a new domain" below).
68
+
69
+ Do only the steps the user asked for. "Add hooks for X" assumes the model and
70
+ repository exist — if they don't, say so and offer to generate them first, in
71
+ order. Never invent a domain name; if the root object's name is unclear, ask.
72
+
73
+ ### Decide first: full model or map-case?
74
+
75
+ Not every endpoint deserves a Resource + Entity. Decide before Step 1:
76
+
77
+ - **Full model (Resource + Entity)** — for a real domain object: something reusable,
78
+ with an identity/lifecycle, or a structural object nested inside another (a country,
79
+ a payment method, a translation, a shared stored file…). It gets `resources/<domain>/`
80
+ + `entities/<domain>/` (Step 1) and its `select` wraps in the Entity.
81
+ - **Map-case (no Resource/Entity)** — for a short, flat, single-endpoint payload with no
82
+ domain identity (a status/feature-flags/health-style response). Skip Step 1 entirely:
83
+ the only type is `<Op>Response` (snake_case) in the repository's `.types.ts`, and the
84
+ hook's `select` renames it into a camelCase plain object inline. See the `health`
85
+ reference (`references/repository/health/`, `references/hooks/use-api-health.ts`).
86
+
87
+ When in doubt, prefer a full model — but don't manufacture an Entity for a one-off flag
88
+ bag just to follow the pattern.
89
+
90
+ ## Step 1 — Model (Resource + Entity)
91
+
92
+ Follow `references/model/`. Core rules:
93
+
94
+ - **Resource** mirrors the wire format exactly — copy backend keys verbatim
95
+ (snake_case stays snake_case), every field optional (`?`), extends
96
+ `BaseResource`. Never add `id`/`created_at`/`updated_at`/`is_skeleton` (inherited).
97
+ Express optionality with `?` alone — don't also union `| null`; a `?` field is
98
+ already absent-or-value, and the Entity default handles `null` and `undefined` the
99
+ same way.
100
+ - **Entity** is a class extending `BaseEntity<Resource>`, every property
101
+ camelCase with a safe default. Apply the semantic renames:
102
+ - dates end in `at` (`publish_date → publishedAt`),
103
+ - arrays end in `List` (`comments → commentList`),
104
+ - booleans start with `is/has/did/can/should/was/will`.
105
+ - Defaults: `string → ''`, `number → 0`, `boolean → false`, `Date → new Date()`,
106
+ nested entity → `new ChildEntity(resource?.field, this)`, array → `[]`.
107
+ - **Every object goes through the model: one object = one Resource + one Entity, each
108
+ in its own file — reused, never inlined, never duplicated.** A nested object is never
109
+ written inline in its parent; it gets its own files in the parent's folder named
110
+ `<parent>-<child>.ts` (e.g. `label-translation.ts` under both `resources/label/` and
111
+ `entities/label/`) and is re-exported from that folder's barrel. The parent **Resource**
112
+ references the child Resource type; the parent **Entity** wraps it through the child
113
+ Entity (`new ChildEntity(resource?.field, this)`, arrays via `.map(...)`) and exposes the
114
+ Entity — never a raw nested resource. An object used in more than one place — its own
115
+ endpoint **and** embedded elsewhere (e.g. a stored file returned standalone *and* nested
116
+ under a label) — is **shared**: lift it to its own top-level `resources/<name>/` +
117
+ `entities/<name>/` folder and import it everywhere (response types included). If you
118
+ catch the same fields written twice, stop and extract — and since `BaseResource` already
119
+ supplies `id`/`created_at`/`updated_at`, a re-declared inline shape is a correctness bug
120
+ too. See `references/model/` (`post`/`user`) for the file-per-object layout.
121
+ - Dependency direction is **entities → resources only**; string enums are union
122
+ types, never `enum`.
123
+
124
+ For the full case-conversion table and nested/multi-file examples, read
125
+ `references/model/` (resources + entities for `post`/`user`).
126
+
127
+ ## Step 2 — Repository
128
+
129
+ Follow `references/repository/post/`. Core rules:
130
+
131
+ - The class extends `ApiInstance`; methods call `this.get/post/put/patch/delete`.
132
+ - **Return the raw server response unchanged** — `return await this.get<...ApiResponse>(...)`.
133
+ Do not unwrap envelopes or reshape into `{ list }`/`{ item }`, and never construct
134
+ entities here: that is the hook's `select` job, so the cache holds the plain wire
135
+ payload (typically `{ data?: Resource[] }` for a list, the flat object for a
136
+ singleton). The repository's *only* transformation is request-side (see next bullet).
137
+ - **The repository is the only camelCase ↔ snake_case boundary.** Destructure the
138
+ explicit camelCase fields plus `options` from the config, then build `params`/`body`
139
+ by hand, mapping each key to its snake_case wire name — so `options` never leaks into
140
+ the URL/body and the wire shape is written in exactly one place:
141
+ `async getXList({ isActive, options }: GetXListApiConfig)` then
142
+ `this.get('/', { params: { is_active: isActive }, options })`. Writes mirror it:
143
+ `async createX({ title, options }: CreateXApiConfig)` then
144
+ `this.post('/', { body: { title }, options })`. No automatic key transform lives in
145
+ the transport — keep it explicit per method.
146
+ - No `try/catch` — errors propagate to the centralized `onError`.
147
+ - Mark each method `// auth` or `// no auth`.
148
+
149
+ ### Types file (`<domain>.types.ts`)
150
+
151
+ - Bare types first, then `// config` and `// response` sections (the `Api*` wrappers).
152
+ - **Four types per operation**, following `references/repository/post/post.types.ts`:
153
+ - `<Op>Config` — the bare request fields in **camelCase** (`{}` when the endpoint
154
+ takes none). This is the public type the query factory consumes.
155
+ - `<Op>Response` — the bare response in **snake_case**, mirroring the server's wire
156
+ shape (a list is typically enveloped as `{ data?: Resource[] }`; a singleton/map is
157
+ the flat object). This is the type the hook's `select` consumes.
158
+ - `<Op>ApiConfig = ApiConfig<<Op>Config>` — adds `options?`; the method's parameter type.
159
+ - `<Op>ApiResponse = ApiResponse<<Op>Response>` — the method's return type.
160
+ - Base wrappers live in `api.types.ts`: `ApiConfig<C> = C & { options? }`,
161
+ `ApiResponse<R> = R`. ID fields use `Entity['id']`, not plain `string`.
162
+ - **The barrel (`index.ts`) re-exports the repository class plus both bare types —
163
+ `<Op>Config` and `<Op>Response`.** The query factory imports `<Op>Config`; the hook
164
+ imports `<Op>Response` to type its `select`/`placeholderData`. The `Api*` wrappers stay
165
+ internal to the repository (imported over the relative `<domain>.types` path).
166
+
167
+ ### Method naming
168
+
169
+ Every method returns the raw `<Op>ApiResponse` (or nothing). The consumer-facing
170
+ `{ list }`/`{ item }` shape is produced later by the hook's `select`, not here.
171
+
172
+ | Verb | Method | Returns (raw wire payload) |
173
+ | ---- | ------ | -------------------------- |
174
+ | GET list | `get<Domain>List` | `{ data?: Resource[] }` |
175
+ | GET one | `get<Domain>` | `{ data?: Resource }` |
176
+ | POST | `create<Domain>` | `{ data?: Resource }` or nothing |
177
+ | PUT/PATCH | `update<Domain>` | `{ data?: Resource }` or nothing |
178
+ | DELETE | `delete<Domain>` | nothing |
179
+
180
+ ## Step 3 — Queries
181
+
182
+ Follow `references/queries/post.ts`. Each domain exports one `<domain>Queries`
183
+ object — the query-key factory plus `queryOptions` builders:
184
+
185
+ - `key` — the base key array, built from `QUERY_KEYS.<DOMAIN>`. Used for
186
+ invalidation (`invalidateQueries({ queryKey: postQueries.key })`).
187
+ - One builder per read endpoint, named for the read it does — `list` for a
188
+ collection, `detail` for a single object (pass an id for a by-id endpoint, or call
189
+ it with no args for a sole-instance/singleton endpoint). Each returns
190
+ `queryOptions({ queryKey, queryFn, … })`.
191
+ - A read builder takes the bare `<Op>Config` (`config: GetXListConfig = {}`) and
192
+ **puts it in the key** — `[...key, config]` for a filtered list, `[...key, id]` for a
193
+ detail (a singleton's key is just `key`). Different params must not share a cache entry.
194
+ - `queryFn: (options) => Api.<domain>Repository.method({ ...config, options })` — the
195
+ whole TanStack Query context is forwarded as `options`; it carries the `signal`, so
196
+ unmounts/invalidations cancel in-flight `fetch`es.
197
+ - Add `staleTime: STALE_TIME` and `meta: { persist: true }` **only** for
198
+ slow-changing reference data. Omit both for always-fresh or user-specific data.
199
+
200
+ `queryOptions` is what lets the same definition feed `useQuery`, `prefetchQuery`,
201
+ and `queryClient.setQueryData` with full type inference — never inline the
202
+ queryKey/queryFn in the hook.
203
+
204
+ ## Step 4 — Hooks
205
+
206
+ Follow `references/hooks/`. Hooks are thin and own only the presentation concern
207
+ (`select` → shape + camelCase/entities, placeholder skeleton):
208
+
209
+ - **One hook per endpoint/operation — never a facade hook that bundles several
210
+ operations.** A hook wraps exactly one `useQuery` or one `useMutation` and (for
211
+ mutations) returns it directly. Don't return `{ create, update, remove }` or several
212
+ mutations from one hook: it forces every consumer to instantiate operations it doesn't
213
+ use and defeats per-call `select`/options.
214
+ - **Hooks are always prefixed `useApi` and named `useApi<Domain><Operation>`** — reads
215
+ `useApi<Domain>` / `useApi<Domain>List`; writes `useApi<Domain><Op>` (e.g.
216
+ `useApiPostCreate`, `useApiPostUpdate`). A domain's operation hooks are **grouped in its
217
+ `use-api-<domain>(s).ts` file**, each exported separately — never split one operation per
218
+ file. A domain with three write endpoints exports three hooks from `use-api-<domain>.ts`
219
+ (e.g. `use-api-oauth.ts` exporting `useApiOauthAuth`, `useApiOauthLink`,
220
+ `useApiOauthCheck`), not one hook returning three mutations and not three separate files.
221
+ - **Every read hook MUST run its result through `select` — no exceptions, including
222
+ single-value / "simple" endpoints.** The cache holds the raw snake_case wire payload;
223
+ the consumer must only ever see a **camelCase `{ item }` / `{ list }`**. A `useQuery`
224
+ without `select` leaks snake_case to the UI — that is a bug. `select` does **both jobs
225
+ the repository deliberately skipped**: it shapes the payload into `{ list }`/`{ item }`
226
+ **and** converts to camelCase. There are two flavors:
227
+ - **Entity-domain** — wrap each resource in its Entity, unwrapping the envelope:
228
+ `{ list: data.data?.map((post) => new PostEntity(post)) ?? [] }` for a list,
229
+ `{ item: new PostEntity(data.data) }` for a single object.
230
+ - **Map-case (no Entity)** — for a short flat endpoint with no domain identity (a
231
+ status/flags/health-style payload), there is no Resource/Entity; `select` just renames
232
+ fields into a camelCase plain object: `{ item: { startAt: data.start_at } }`. See
233
+ `references/hooks/use-api-health.ts`.
234
+ - Read query: `useQuery({ ...postQueries.list(), placeholderData: PLACEHOLDER_DATA, select: selectPostList })`.
235
+ **`placeholderData` is mandatory on every read hook — lists, single objects, and
236
+ map-cases alike, not just lists.** Its job is not only the skeleton: it guarantees the
237
+ query always holds data in the wire shape, so `select` always runs it through the Entity
238
+ (or the map-case rename) and the consumer always receives a fully-defaulted
239
+ `{ item }` / `{ list }` instead of `undefined` during load. That means callers read
240
+ `data.item` / `data.list` directly — never `data?.item`, never a manual default. Both
241
+ `placeholderData` and `select` are required; neither is ever omitted.
242
+ - **`select` is a module-level function** (stable reference). Never inline it — an inline
243
+ `select` re-runs every render and, because entities break structural sharing, causes
244
+ constant re-renders.
245
+ - The `select` input type is the bare **`<Op>Response`** imported from the repositories
246
+ barrel (`import { type GetPostListResponse } from '../repositories'`) — this is what
247
+ the cache holds. (That is why the barrel exports `<Op>Response`.)
248
+ - `PLACEHOLDER_DATA` is a module-level constant typed as that same `<Op>Response`, holding
249
+ **plain default data in the wire shape** — never entities. Its exact shape follows the
250
+ endpoint:
251
+ - **list** — a few skeleton items: `{ data: [{ is_skeleton: true }, …] }`;
252
+ - **single object** — one skeleton resource: `{ data: { is_skeleton: true } }`;
253
+ - **map-case** — the flat wire object with default field values (`{}` when every field
254
+ is optional).
255
+
256
+ `select` runs it through the Entity (or the map-case rename), so before the server
257
+ answers the consumer already has a default entity with `isPending: true` and safe
258
+ defaults. Use `placeholderData`, not `initialData`.
259
+
260
+ ### Which hook for which endpoint
261
+
262
+ | Endpoint | Hook |
263
+ | -------- | ---- |
264
+ | List / single fetched on mount | `useQuery` (+ `select`) |
265
+ | Create / update / delete | `useMutation` |
266
+
267
+ One row = one hook. A domain with three write endpoints gets three hooks —
268
+ `useApi<Domain><Op>` each, all grouped in `use-api-<domain>.ts` — never one hook returning
269
+ three mutations.
270
+
271
+ Default mutations invalidate on settle (see `useApiPostCreate` in
272
+ `references/hooks/use-api-post.ts`):
273
+ `onSettled: () => queryClient.invalidateQueries({ queryKey: postQueries.key })`,
274
+ with `queryClient` imported from `../api.client`.
275
+
276
+ #### Mutations that return data the caller uses — map in `mutationFn`
277
+
278
+ `useMutation` has **no `select`** (that's a `useQuery` option). So when a mutation's
279
+ response carries data the caller reads from `mutation.data` (not just fire-and-invalidate),
280
+ do the **same mapping a query `select` does, inside `mutationFn`** — the repository still
281
+ returns the raw `<Op>Response`; the hook awaits it and runs it through a module-level
282
+ mapping function before returning:
283
+
284
+ ```ts
285
+ // module-level — the mutation analog of a query `select`: shape to { item } / { list }
286
+ // and snake_case → camelCase (Entity-wrap a modelled domain, plain camelCase for a map-case)
287
+ const selectPostCreate = (data: CreatePostResponse) => {
288
+ return { item: new PostEntity(data.data) }
289
+ }
290
+
291
+ const useApiPostCreate = () => {
292
+ return useMutation({
293
+ mutationFn: async (config: CreatePostConfig) =>
294
+ selectPostCreate(await Api.postRepository.createPost(config)),
295
+ onSettled: () => queryClient.invalidateQueries({ queryKey: postQueries.key }),
296
+ })
297
+ }
298
+ ```
299
+
300
+ One shared mapping function can serve all of a domain's mutation hooks. The rule "build
301
+ entities/shape only in `select`, never in the repository" holds — for mutations the
302
+ `mutationFn` is where that mapping runs; the repository stays a raw pass-through.
303
+
304
+ ### Optimistic mutations (when instant feedback matters)
305
+
306
+ Add `onMutate`/`onError` around the default:
307
+
308
+ 1. `await queryClient.cancelQueries({ queryKey: postQueries.key })` — stop
309
+ in-flight refetches from overwriting the optimistic state.
310
+ 2. Snapshot: `const previous = queryClient.getQueryData(postQueries.list().queryKey)`.
311
+ 3. Write optimistically with `setQueryData` — **into the raw wire shape the cache
312
+ holds** (`{ data?: PostResource[] }`), not the `{ list }`/entity shape; `select`
313
+ re-derives the shaped entities on read. Return `{ previous }`.
314
+ 4. `onError` rolls back: `setQueryData(postQueries.list().queryKey, context?.previous)`.
315
+ 5. `onSettled` invalidates to reconcile with the server.
316
+
317
+ Use `meta: { silent: true }` on a mutation whose error you handle locally, so the
318
+ global `onError` stays quiet.
319
+
320
+ ## Step 5 — Mock (on request)
321
+
322
+ Follow `references/mock/post.mock.ts`. Fixtures use **wire-format (Resource) field
323
+ names**, returned as deep clones; an entity helper round-trips them through the
324
+ Entity class the way production does. Reuse an existing `mock`/`fixtures` dir; ask
325
+ before inventing a new root.
326
+
327
+ ## Wiring a new domain into shared files
328
+
329
+ After generating the domain, update (see `references/client/overview.md` for the
330
+ full picture):
331
+
332
+ 1. `api.ts` — `static <domain>Repository = new <Domain>Repository('/<domain>s')`.
333
+ 2. `constants/index.ts` — add `<DOMAIN>S: '<domain>s'` to `QUERY_KEYS`.
334
+ 3. Barrels — one alphabetical re-export line in each of `resources/index.ts`,
335
+ `entities/index.ts`, `repositories/index.ts`, `queries/index.ts`, `hooks/index.ts`.
336
+ 4. Prefetch (optional) — reference data shown on first screens gets a
337
+ `queryClient.prefetchQuery(<domain>Queries.list())` in the startup prefetch.
338
+
339
+ ## Verify
340
+
341
+ After generating, the tree must pass `npm run type`, `npm run lint`, and
342
+ `npm run format` cleanly (per the prettier-eslint-typescript rule). A common miss:
343
+ forgetting a barrel export, or leaving `options` un-destructured in a repository
344
+ method.
345
+
346
+ ## Checklist before finishing
347
+
348
+ - [ ] Resource fields match the backend wire format exactly; all optional
349
+ - [ ] Entity is camelCase with defaults; dates end `at`, arrays end `List`, booleans prefixed
350
+ - [ ] Every distinct/nested/shared object has its own Resource+Entity **in its own file**, reused and wrapped through its Entity in the parent — no inline/raw nested shapes, no duplicates
351
+ - [ ] Repository returns the **raw wire response** unchanged — no `{ list }`/`{ item }` reshaping, no entities, no try/catch
352
+ - [ ] Repository maps camelCase → snake_case by hand into `params`/`body`, keeping `options` separate
353
+ - [ ] Per-op types `<Op>Config` (bare camelCase) / `<Op>Response` (bare snake_case wire shape) / `<Op>ApiConfig` / `<Op>ApiResponse`; IDs use `Entity['id']`; barrel exports the bare `<Op>Config` **and** `<Op>Response`
354
+ - [ ] `queries/<domain>.ts` exports `<domain>Queries` with `key` + `list`/`detail` builders; key includes `config`; `queryFn` forwards the context as `options` (`{ ...config, options }`)
355
+ - [ ] `staleTime: STALE_TIME` / `meta.persist` set only for reference data
356
+ - [ ] One hook per endpoint/operation, prefixed `useApi<Domain><Operation>`, all grouped in `use-api-<domain>.ts`; no facade hook returning multiple mutations, no one-operation-per-file split
357
+ - [ ] **Every** read hook runs through a module-level `select` that both shapes `{ list }`/`{ item }` and converts to camelCase — Entity-wrap for domains, plain camelCase object for map-cases (no `useQuery` without `select`)
358
+ - [ ] **Every** read hook passes `placeholderData` (list, single, and map-case alike) so `select` always runs and the consumer always gets a defaulted `{ item }`/`{ list }`, never `undefined`
359
+ - [ ] `select` input typed as the bare `<Op>Response` from the repositories barrel; entities built only in `select`, never in repository or `queryFn`
360
+ - [ ] Mutation whose `mutation.data` is consumed maps the raw response inside `mutationFn` (the `useMutation` analog of `select`, since there is no `select` option); repository stays raw
361
+ - [ ] Shared files wired: `api.ts`, `QUERY_KEYS`, all barrels
362
+ - [ ] `npm run type` / `lint` / `format` clean