@mediacubeco/expo-template-fsd 2.3.1 → 2.4.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 (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 +13 -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
@@ -1 +1,1025 @@
1
- TODO: fill
1
+ ---
2
+ name: code-style
3
+ description: >-
4
+ Project code style rules: naming (List/Count suffixes, boolean prefixes,
5
+ plural dictionaries), exports at end of file, mandatory use of @mediacubeco
6
+ utilities, useXxxStyles pattern, the visual token map (COLORS roles and
7
+ alpha steps, SIZES spacing rhythm and computed radii, the Text typography
8
+ scale, Glass/Blur materials, ANIMATIONS timing/spring tokens, Touch press
9
+ feedback props withRipple / withSpring / withHaptics, the selection-option
10
+ scaffold), custom React hooks (use-* files, facade hooks), memoization
11
+ policy, JSX prop conventions, thin route files in src/app (Expo Router, Page
12
+ components, sheet/web options), the keep-src/app-empty rule, Zustand client
13
+ state (stores, persist, selectors), the folder-per-page scaffold in
14
+ src/pages, mandatory pull-to-refresh (onRefresh) on screens with server
15
+ data, keyboard UX for multi-input forms (useFormFields), the translations
16
+ workflow (source of truth in ../front/lang, generated assets), and the
17
+ scaffold for shared UI components in src/shared/ui (atoms, molecules,
18
+ organisms, templates).
19
+ Use this skill EVERY time TypeScript/TSX code is written, edited, refactored,
20
+ or reviewed in this project — new components, hooks, API layers, pages, bug
21
+ fixes, and small edits alike. Also use when the user asks to create a
22
+ component or UI element, to add a translation key, to build or edit a form,
23
+ to add a Zustand store, or to check or fix code style, naming, or
24
+ consistency.
25
+ ---
26
+
27
+ # Code Style
28
+
29
+ These rules describe how code is written in this project. They exist so that any
30
+ file reads as if one person wrote it. When editing existing code, follow these
31
+ rules for the code you touch; older code may deviate — the rules win.
32
+
33
+ ## Naming
34
+
35
+ ### Variables
36
+
37
+ - **Arrays always end with `List`** — variables, props, entity fields, function
38
+ return names. The suffix tells the reader "iterable" without looking at the type.
39
+ - ✅ `countryList`, `dataList: string[]`, `getCountryList()`, `snapToOffsetList`
40
+ - ❌ `countries`, `data: string[]`, `items`
41
+ - Exception: a generic list component whose element type is the generic
42
+ parameter keeps the platform-conventional `data: Item[]` prop (matching
43
+ RN's `FlatList`-style API).
44
+ - **Booleans start with `is` / `can` / `did` / `has` / `should` / `with`** —
45
+ everywhere: variables, props, fields. `isPending`, `isDisabled`,
46
+ `didFinalized`, `canSubmit`, `withSpring`, `withHaptics`, `withAvatar`.
47
+ - Exception: controlled selection components (`Switch`, `Checkbox`, `Radio`)
48
+ keep the platform-conventional `value?: boolean` prop.
49
+ - **Dictionary/Record objects are plural** — objects whose keys map to a set of
50
+ same-shaped values: `QUERY_KEYS`, `COLORS`, `BREAKPOINTS`, `TRANSPARENCIES`.
51
+ - **Counters use the `Count` suffix**: `tabCount`, `itemCount`
52
+ (not `numberOfTabs`, not `length` copies without a name).
53
+ - **Design constants are UPPER_SNAKE_CASE even when computed inside a hook.**
54
+ Anything that plays the role of a layout/design token — sizes, paddings,
55
+ widths — keeps the constant casing regardless of where it is derived:
56
+ `TAB_WIDTH`, `TABS_PADDING`, `SLIDER_ITEM_WIDTH`.
57
+
58
+ ### Functions and props
59
+
60
+ - Internal handlers: `handleX` (`handlePress`, `handleWrapperLayout`).
61
+ - Callback props: `onX` (`onPress`, `onChange`). A component receives `onX`,
62
+ wraps it in `handleX`, and passes `handleX` down.
63
+ - A prop that shadows an internal name gets the `init` prefix when destructured:
64
+ `header: initHeader`, `renderItem: initRenderItem`.
65
+
66
+ ### Types
67
+
68
+ - Object shapes are described with type aliases only — no `interface`.
69
+ - **`enum` is welcome** for a fixed set of related constants (theme modes,
70
+ locale codes, transport methods): string values, `PascalCase` name,
71
+ `PascalCase` members, Russian JSDoc like any other top-level declaration.
72
+ A union type is for ad-hoc unions of literals/types that don't form a
73
+ named constant set — don't degrade an `enum` into a union.
74
+
75
+ ```ts
76
+ /** Способ доставки уведомления, выбранный пользователем. */
77
+ enum NotificationChannel {
78
+ Push = 'push',
79
+ Email = 'email',
80
+ Sms = 'sms',
81
+ }
82
+ ```
83
+ - **A constant lookup map is typed by `as const`, never by an explicit
84
+ `Record<…>` annotation.** Appending `as const` preserves the literal value
85
+ types and makes the map `readonly`; a `Record<K, V>` annotation widens every
86
+ value to `V` (losing the literals) and forces you to restate the key set by
87
+ hand. Let TypeScript infer — the object _is_ the type.
88
+
89
+ ```ts
90
+ // ❌ explicit Record annotation — widens values, restates keys
91
+ const VALUE_BY_KEY: Record<Key, Value> = {
92
+ alpha: 'first',
93
+ beta: 'second',
94
+ gamma: 'third',
95
+ }
96
+
97
+ // ✅ as const — literal values, inferred keys, readonly
98
+ const VALUE_BY_KEY = {
99
+ alpha: 'first',
100
+ beta: 'second',
101
+ gamma: 'third',
102
+ } as const
103
+ ```
104
+ - Component props: `<Name>Props`. Styles-hook context: `<Name>StylesContext`
105
+ (plural `Styles`, matching the hook name).
106
+ - Refs are passed via a plain `ref?: Ref<T>` prop (React 19 treats `ref` as a
107
+ regular prop) — never `forwardRef` (it is deprecated). The component forwards
108
+ `ref` to the most useful inner element.
109
+
110
+ ### Files
111
+
112
+ - All files and folders are **kebab-case**: only lowercase letters, digits, and
113
+ hyphens (`some-name.ts`). No camelCase, PascalCase, or snake_case. Dots only
114
+ for extensions and config modifiers (`eslint.config.js`).
115
+ - Hooks files start with `use-` (`use-breakpoints.ts`).
116
+ - A shared UI component is a 4-file folder — see "Shared UI components" below.
117
+
118
+ ## Imports and exports
119
+
120
+ - **All exports live at the very end of the file**, after imports, types, and
121
+ implementation. Never scatter `export` on declarations (`export const`,
122
+ `export function`, `export class`) — declare first, then export in one block
123
+ at the bottom: it makes the file's public API readable at a glance.
124
+ - Order at the bottom: `export { … }` → `export type { … }` →
125
+ `export default …` last.
126
+ - `export default` only for React components (and Expo Router pages).
127
+ - Type imports are inline: `import { type TextProps } from './text.types'`.
128
+ - Barrel `index.ts` files contain only re-exports (`export … from`), and the
129
+ lines are sorted **alphabetically by module path**, top to bottom (A→Z / а→я).
130
+ There is no "newest goes last" — adding an export means inserting it into its
131
+ alphabetical slot, not appending to the end. This holds for every barrel:
132
+ segment barrels (`src/shared/ui/<layer>/index.ts`), `hooks/` barrels, and
133
+ component `index.ts` files alike.
134
+
135
+ ```ts
136
+ export * from './alert'
137
+ export * from './avatar'
138
+ export * from './badge'
139
+ export * from './button'
140
+ ```
141
+
142
+ ## @mediacubeco libraries
143
+
144
+ The `@mediacubeco/*` packages are the project's utility belt. **Everything that
145
+ exists there must be imported from there — duplicating or re-implementing it is
146
+ a defect.** The packages evolve between versions, so never rely on a memorized
147
+ list; inspect the installed version:
148
+
149
+ - Discover the export surface via the DTS indexes in `node_modules`:
150
+ `@mediacubeco/base` (`dist/index.d.ts`, subpaths `/helpers`, `/services`,
151
+ `/types`, `/utils`) and `@mediacubeco/base-react-native` (subpaths
152
+ `/helpers`, `/hooks`, `/utils`). Grep `from '@mediacubeco/` across `src/`
153
+ to see the patterns in active use.
154
+ - Always import via subpath, never from the package root:
155
+ `@mediacubeco/base/utils` ✅, `@mediacubeco/base` ❌.
156
+ - **Before hand-rolling any utility hook or helper — layout measurement,
157
+ keyboard, app-state, debounce, guards, and the like — inspect the library
158
+ first and use what it already provides; re-implementing what exists there is a
159
+ defect.** Read the installed exports and their return shapes (per the DTS
160
+ inspection above) instead of assuming an API — the package evolves, so no
161
+ specific export is guaranteed to exist: discover it, don't memorize it.
162
+ - Optional callbacks are invoked via the package's callback utility, never
163
+ `onPress?.(event)`.
164
+ - Never copy source from a `@mediacubeco/*` package into the app. If something
165
+ is missing, add it to the package first, then import it.
166
+ - ESLint, Prettier, and TypeScript configs extend the corresponding
167
+ `@mediacubeco/*` config package — never inline equivalent rules.
168
+
169
+ ## Components
170
+
171
+ - Arrow-function components: `const Name = (props: NameProps) => { ... }`,
172
+ default-exported at the bottom.
173
+ - **All visual logic lives in `useXxxStyles()`** — not just `StyleSheet.create`,
174
+ but also layout measurement, shared values, `useAnimatedStyle`, derived design
175
+ constants, and layout/scroll handlers. The component file keeps only behavior
176
+ (gestures, callbacks) and markup. This keeps `.tsx` declarative and lets the
177
+ styles hook be read as the single source of visual truth.
178
+ - All visual values come from theme tokens via `useThemeMode()`. Hardcoding
179
+ any visual value is forbidden — if it affects how something looks, it comes
180
+ from a token. Never inline styles in JSX.
181
+ - **Theme and measurement hooks never appear in the component file** —
182
+ `useTheme`, `useThemeMode`, `useSafeArea`, `useBreakpoints`, `useMixins` and
183
+ the like are called only inside `useXxxStyles()`. If the component needs a
184
+ dynamic color or size (e.g. `placeholderTextColor`), the styles hook returns
185
+ it.
186
+ - The styles hook returns **only what the component actually uses** —
187
+ `{ styles }` plus animated styles, design constants, handlers, colors as
188
+ needed. Nothing speculative.
189
+ - **Prefer a parameterless styles hook — `useXxxStyles()` with no arguments.**
190
+ When a visual variation is a fixed, finite set (sizes, states, variants),
191
+ build **every** variant inside the hook (e.g. a `stylesBySize` map keyed by
192
+ the variant) and let the component pick the relevant one by key — don't
193
+ thread the current variant into the hook as a context. A hook argument
194
+ (a `<Name>StylesContext`) is justified only when a style depends on a runtime
195
+ value that _cannot_ be enumerated ahead of time (a measured dimension, a
196
+ free-form color). If you can pass nothing, pass nothing.
197
+
198
+ ```tsx
199
+ // hook builds all variants, takes no arguments
200
+ const useCardStyles = () => {
201
+ const { SIZES } = useThemeMode()
202
+
203
+ const stylesBySize = StyleSheet.create({
204
+ small: { width: SIZES['6XL'], borderRadius: SIZES['6XL'] },
205
+ large: { width: SIZES['14XL'], borderRadius: SIZES['14XL'] },
206
+ })
207
+
208
+ return { stylesBySize }
209
+ }
210
+
211
+ // component selects the variant by key
212
+ const Card = ({ size = 'small' }: CardProps) => {
213
+ const { stylesBySize } = useCardStyles()
214
+
215
+ return <View style={stylesBySize[size]} />
216
+ }
217
+ ```
218
+
219
+ ### Shared UI components (`src/shared/ui`)
220
+
221
+ Canonical scaffolds live in `references/components/` — read them before
222
+ creating a component:
223
+
224
+ - `references/components/post/` — simple custom component: own props, `Text`
225
+ atom with variants instead of raw RN `Text`, styles only for layout.
226
+ - `references/components/wrap/` — **native wrapper pattern**: extends the
227
+ native props with `Omit<NativeProps, 'style'>`, swallows `style` in
228
+ destructuring, exposes the `outerStyle` / `innerStyle` pair (inner `View`
229
+ wraps children), forwards `ref` to the native element, defaults set in
230
+ destructuring (`decelerationRate = 'fast'`).
231
+
232
+ Rules:
233
+
234
+ - Place at the lowest atomic layer that fits: `atoms/` (single-purpose
235
+ primitive) → `molecules/` (combines atoms) → `organisms/` (self-contained
236
+ block) → `templates/` (full-screen scaffold).
237
+ - Four files per component: `<name>.tsx`, `<name>.types.ts`, `<name>.styles.ts`,
238
+ `index.ts`. The `index.ts` re-exports explicitly:
239
+ `export { default as <Name> } from './<name>'` and
240
+ `export type { <Name>Props } from './<name>.types'` — named types only,
241
+ `*StylesContext` stays internal.
242
+ - Register the component in the parent layer barrel
243
+ (`src/shared/ui/<layer>/index.ts`), alphabetically.
244
+ - `<name>.types.ts` exports only types. Props type even when empty
245
+ (`type KeyboardAreaProps = {}`). When the styles hook does take a context
246
+ (only for non-enumerable runtime values — see "Prefer a parameterless styles
247
+ hook" above), it is derived from props:
248
+ `type <Name>StylesContext = Required<Pick<<Name>Props, '…'>> & {}`.
249
+ - **One file, one component — every subcomponent lives in its own file.** A
250
+ component declared inside another component's file is a defect: extract it.
251
+ Any subcomponent internal to a component — of **any** layer (atom, molecule,
252
+ organism, template), not just organisms — lives in a nested `components/`
253
+ folder (`<name>/components/<subcomponent>/…`), each subcomponent as its own
254
+ 4-file folder with its own `index.ts`; a `components/index.ts` barrel
255
+ re-exports them alphabetically. Never keep several components in one file.
256
+ - Multi-variant components keep one folder with a file per variant plus shared
257
+ `<name>.types.ts(x)`, `<name>.styles.ts`, and `<name>.hooks.tsx` for shared
258
+ prop-building logic.
259
+ - Compose from existing shared atoms (`Text` with a `variant` instead of raw
260
+ RN `Text`); typography lives in the `Text` atom, not in component styles.
261
+
262
+ ### Memoization
263
+
264
+ - `useCallback` — **always**, for every handler.
265
+ - `useMemo` — only for genuinely expensive computations (building arrays,
266
+ gesture configs). Trivial derivations stay plain.
267
+ - `memo()` on components — only for heavy components; not by default.
268
+
269
+ ### JSX
270
+
271
+ - Prop values are always explicit and braced: `horizontal={true}`,
272
+ `decelerationRate={'fast'}`. Never bare booleans or quoted strings.
273
+ - **Conditional rendering of a single branch uses `&&`** — a ternary with
274
+ `: null` is noise. The ternary is reserved for a genuine two-way choice
275
+ (two real render branches or two values).
276
+
277
+ ```tsx
278
+ // ✅ one branch — &&
279
+ {
280
+ value && (
281
+ <Wrap>
282
+ <Component />
283
+ </Wrap>
284
+ )
285
+ }
286
+
287
+ // ❌ one branch — ternary with null
288
+ {
289
+ value ? (
290
+ <Wrap>
291
+ <Component />
292
+ </Wrap>
293
+ ) : null
294
+ }
295
+
296
+ // ✅ two branches — ternary
297
+ {
298
+ isLoading ? <Spinner /> : <Content />
299
+ }
300
+ ```
301
+
302
+ Guard against accidental `0`/`''` renders by making the condition boolean
303
+ when the value is a number or string count: `{itemCount > 0 && <Badge />}`.
304
+
305
+ - Prop order, both in destructuring and in JSX attributes:
306
+ 1. `ref`
307
+ 2. data — within the group ordered by type: strings → numbers → booleans →
308
+ union values → arrays (`*List`) → objects
309
+ 3. styles (`style`, `outerStyle`, `innerStyle`, `contentContainerStyle`)
310
+ 4. configuration flags
311
+ 5. handlers (`onX`) and render props (`renderItem`) — always last
312
+
313
+ ## Visual tokens
314
+
315
+ How the design decisions from the `design-style` skill are written down in
316
+ code. Every visual value comes from `useThemeMode()` tokens (read only inside
317
+ `useXxxStyles`); a raw hex/rgba, number, duration, or font in a page, widget,
318
+ or feature is a defect — it silently breaks the second theme and the accent
319
+ system.
320
+
321
+ ### Colors (`COLORS`)
322
+
323
+ - Roles, not palettes: `BACKGROUND` (screen planes), `FILL` (surfaces),
324
+ `STROKE` (borders/dividers), `TYPOGRAPHY` (text/icons), plus `BAR` and
325
+ `INPUT` for their namesake components. Pick the token by what the element
326
+ _is_.
327
+ - Intensity steps: base → `_WEAK` (80%) → `_HALF` (40%) → `_STRONG` (20%) →
328
+ `_LIGHT` (10%). A lighter shade of a semantic color is its alpha step,
329
+ never a new color.
330
+ - `PRIMARY*` is derived from the user-chosen accent through the theme
331
+ factories — anything accent-colored uses `PRIMARY` tokens.
332
+ - Paired content colors: content on accent uses `PRIMARY_CONTRAST`, on dark
333
+ surfaces `CONTRAST`, on light surfaces `MATTER` or `TYPOGRAPHY.BASE`.
334
+ - A new color concept is added to the palette and threaded through the
335
+ light/dark factory functions — never placed beside the system as a local
336
+ constant. The test: switching theme mode and accent must recolor it with
337
+ zero extra work at call sites. The single exception — artistic hero-scene
338
+ colors — are named `UPPER_SNAKE_CASE` constants at the top of the owning
339
+ styles file with a JSDoc explaining they are deliberate art, defined once
340
+ (shared scenes share constants).
341
+ - Previews of another theme/accent resolve the actual theme object for that
342
+ mode/accent and read its `COLORS` — never hand-picked approximations.
343
+
344
+ ### Sizes, radii, and spacing (`SIZES`)
345
+
346
+ All radii and spacing come from `SIZES` or are computed from `SIZES`-based
347
+ dimensions, as `UPPER_SNAKE_CASE` constants in the styles hook with a JSDoc
348
+ deriving them (see the radii in
349
+ `references/components/payout-speed-option/payout-speed-option.styles.ts`).
350
+
351
+ - Capsule radius = element height / 2, computed from the same sizes that
352
+ build the element (icon size + paddings).
353
+ - Concentric nesting: `radius = container radius − gap to the container
354
+ edge`; a frame around rounded content: `content radius + frame padding`.
355
+ - Card radii land at `SIZES['5XL']` and above; `SIZES['3XL']` and smaller
356
+ belong to _nested_ tiles inside a card, never to the card itself.
357
+ - The spacing rhythm, outermost to innermost:
358
+
359
+ | Level | Token | Use |
360
+ | --------------- | ----- | -------------------------------------------------------------------------------- |
361
+ | Screen sections | `4XL` | gap between independent blocks on a screen |
362
+ | Inside a card | `2XL` | card padding |
363
+ | Row content | `XL` | gap between icon / text / trailing control in a row; horizontal insets of labels |
364
+ | Small stacks | `L` | gap between sibling tiles, label-to-card margin |
365
+ | Text pairs | `3XS` | value + caption; line height already provides the air |
366
+
367
+ ### Typography (`Text`)
368
+
369
+ All text goes through the `Text` atom — its `type` prop is the entire scale
370
+ (`bigTitle`, `h1`–`h3`, `subtitle`, `body`/`bodySB`,
371
+ `description`/`descriptionSB`, `small`/`smallSB`, `button`), and it already
372
+ carries the tight negative letter-spacing. A raw `fontSize`/`fontFamily` in
373
+ feature styles is a defect. Captions use `variant={'secondary'}`; long
374
+ content uses `lineCount` (cap lines) and `shouldFit` (shrink one-liners).
375
+
376
+ **A text placeholder is sized from the type it replaces.** A skeleton plate
377
+ standing in for a line of `Text` gets the **exact height of that type's line
378
+ box**: the same `SIZES` token as the type's `fontSize`, times the scale's
379
+ line-height ratio (`* 1.2`) — the identical expression the `Text` scale uses to
380
+ compute its own `lineHeight`. So a `bodySB` line → `SIZES['2XL'] * 1.2`, an
381
+ `outlineSB` line → `SIZES['XL-2XL'] * 1.2`, a `small` line →
382
+ `SIZES['L-XL'] * 1.2`. Width may be approximate (a fraction that reads as a line
383
+ of text); height must equal the line box exactly, so the real text drops in with
384
+ zero vertical reflow (design skill, "Loading" — placeholders match final size).
385
+ Never hardcode a skeleton height in raw pixels: mirror the token and the `* 1.2`
386
+ ratio of the covered type, and the placeholder tracks the scale if that type
387
+ ever changes.
388
+
389
+ ### Materials (`Glass`, `Blur`, `SHADOWS`)
390
+
391
+ - The `Glass` atom is used only for the floating navigation layer (screen
392
+ header, bottom bar); the `Blur` atom for scrims and overlays. Everything
393
+ else is opaque on `FILL`/`BACKGROUND` tokens.
394
+ - Shadows come from `SHADOWS`: `THIN` for barely-lifted elements,
395
+ `BASE`/`FAT` only for genuinely floating surfaces.
396
+
397
+ ### Motion (`ANIMATIONS`) and press feedback (`Touch`)
398
+
399
+ - State changes animate over `ANIMATIONS.TIMING.BASE`, micro-feedback uses
400
+ `SHORT`, `LONG` is reserved for screen-scale transitions; springs use
401
+ `ANIMATIONS.SPRING.BASE`. `withTiming()`/`withSpring()` may be called
402
+ without a config (library defaults); when a config **is** passed, its
403
+ values come from `ANIMATIONS` tokens — a raw duration is a defect.
404
+ - The `Touch` atom provides press feedback; pick the prop by role:
405
+ `withRipple` for rows inside containers and small utility targets,
406
+ `withSpring` (scale ~0.95) for self-contained cards and standalone
407
+ elements, `withHaptics` only for significant actions. `Button` animates
408
+ itself — never wrap it in extra feedback.
409
+ - Disabled state goes through `DISABLED` tokens and the component's
410
+ `isDisabled` prop, never a hand-tuned opacity; a disabled element gets no
411
+ feedback props at all.
412
+
413
+ ### The selection scaffold
414
+
415
+ Canonical example: `references/components/payout-speed-option/` — the
416
+ project's selection language in code. Every "pick one of N" component follows
417
+ it:
418
+
419
+ - an animated frame:
420
+ `interpolateColor(STROKE.TRANSPARENT → STROKE.PRIMARY)` on a shared value
421
+ driven by `handleActive`/`handleInactive` over `TIMING.BASE`, with a
422
+ constant `SIZES['S']` border width (color changes, layout never jumps);
423
+ - a check indicator with paired `scale` 0→1 + `opacity` 0→1 on the same
424
+ shared value;
425
+ - `withHaptics` on the commit.
426
+
427
+ ## Hooks
428
+
429
+ Canonical example: `references/hooks/use-posts.ts` — a custom domain hook
430
+ showing the full pattern: a private unexported helper hook, a derived
431
+ `postList` in `useMemo`, a `handleX` callback in `useCallback`, and
432
+ composition of an API hook. Read it before writing any custom hook.
433
+
434
+ - One public hook per file, file named `use-<name>.ts` — the default. Private
435
+ helper hooks may live unexported in the same file (see the unexported
436
+ `useSearch` in `references/hooks/use-posts.ts`). Justified exceptions exist —
437
+ e.g. the API layer groups all hooks of one resource in a single
438
+ `use-api-<resource>.ts` — but don't invent new ones without a reason.
439
+ - Placement: app-wide reusable hooks → `src/shared/hooks/`; hooks of a shared
440
+ lib domain → `src/shared/lib/<domain>/hooks/`; domain-specific hooks → the
441
+ FSD slice that owns the domain. Every `hooks/` folder has an alphabetical
442
+ barrel `index.ts` of `export * from './use-…'` lines.
443
+ - Same export policy as everywhere: `const useX = () => { … }`, then
444
+ `export { useX }` at the bottom — hooks are never default-exported.
445
+ - A hook that exposes several things returns a single object, fields ordered
446
+ like JSX data props (strings → numbers → booleans → arrays → objects,
447
+ handlers last). A hook whose natural API is one thing returns that thing
448
+ directly — `useLocalizationText` returns the `t` function,
449
+ `useNavigationRoute` returns the router facade. Don't force a wrapper
450
+ object around a single value, and don't hand-write (or `ReturnType`-export)
451
+ return types TypeScript already derives.
452
+ - Every function returned from a hook is wrapped in `useCallback`; derived
453
+ arrays/objects in `useMemo` — the same memoization policy as components.
454
+ - Context hooks fail fast outside their provider via `ErrorHandler.throw`
455
+ from `@mediacubeco/base/helpers`. A context designed with a meaningful
456
+ default value (e.g. an availability flag defaulting to
457
+ `{ isAvailable: false }`) may return that default instead of throwing —
458
+ but that must be a deliberate design, not a forgotten check.
459
+ - Third-party hooks are wrapped in thin project facades instead of being
460
+ called at use sites: `useNavigationRoute` wraps `useRouter`,
461
+ `useLocalizationText` wraps `useLocalization`. Call sites depend on project
462
+ vocabulary, and the dependency can be swapped in one file.
463
+ - Component-scoped hooks stay next to their component — `useXxxStyles` in
464
+ `<name>.styles.ts`, shared prop-building `useXxxProps` in
465
+ `<name>.hooks.tsx` — never in a `hooks/` folder.
466
+ - Any work in the API layer (`src/shared/lib/api` — resources, entities,
467
+ repositories, queries, hooks, mocks) goes through the **api** skill, which owns
468
+ the data-layer architecture (plain resources in the repository, entities in
469
+ `select`, `queryOptions` factories, transport options). Don't hand-roll fetch
470
+ calls, query keys, or TanStack Query wiring.
471
+
472
+ ## State
473
+
474
+ Client state uses **Zustand** — a small store per concern. There is no central
475
+ store; state is owned by the slice it belongs to. Canonical example:
476
+ `references/store/use-post-draft-store.ts` — read it before adding or touching
477
+ any store.
478
+
479
+ Pick the smallest tier that fits. Reaching for a store when local state or the
480
+ query cache would do is the most common mistake here:
481
+
482
+ 1. **Server state → TanStack Query.** Anything from the API — lists, entities,
483
+ mutations, their loading/error — lives in the query cache and goes through
484
+ the **api** skill. Never copy server data into a store; a store that
485
+ mirrors the server is a defect.
486
+ 2. **Client state that outlives one component _or_ is read/written outside
487
+ React → a Zustand store** (session tokens, a multi-step form draft,
488
+ cross-screen UI state).
489
+ 3. **Everything else → local `useState` / `useReducer`, or React Context** for
490
+ DI-style values. Theme, navigation, and websocket already use Context —
491
+ that is correct, do not migrate them to a store.
492
+
493
+ ### Where a store lives
494
+
495
+ - Domain state → the **`model/` segment of the owning slice**
496
+ (`features/<name>/model/`, `entities/<name>/model/`).
497
+ - Cross-cutting / app-global state (e.g. the auth session) → the dedicated
498
+ **`src/shared/lib/store/`** lib.
499
+ - File is kebab-case `use-<domain>-store.ts`; the hook is `use<Domain>Store`;
500
+ re-export it from the segment barrel. One store = one concern — small,
501
+ independent stores rather than one big object.
502
+
503
+ ### Writing a store
504
+
505
+ - `create<State>()(…)` with a typed state: boolean prefixes and `List`
506
+ suffixes per the naming rules, actions as methods on the state object,
507
+ Russian JSDoc on the state type, every field, and every action.
508
+ - Persist **only** state that must survive a restart: `persist` middleware +
509
+ `createJSONStorage` pointed at the shared adapters from
510
+ `~shared/lib/storage` — never instantiate MMKV, SecureStore, or AsyncStorage
511
+ inside a slice. Sensitive data (tokens) → `storageSecureAsync`;
512
+ non-sensitive → `storageAsync`. Use `partialize` to keep transient fields
513
+ out of storage (see the reference).
514
+
515
+ ### Consuming a store
516
+
517
+ - In components — **always through a selector**:
518
+ `useSessionStore((state) => state.accessToken)`. Never grab the whole store
519
+ (`const state = useSessionStore()`) — that re-renders on every field change.
520
+ - Outside React — the vanilla store API: `useSessionStore.getState()`,
521
+ `.setState()`, `.subscribe()`. This is the sanctioned way to reach client
522
+ state from non-React code (e.g. the API transport attaching the
523
+ `Authorization` header).
524
+
525
+ ### Boundaries
526
+
527
+ - **Stores never import each other.** A cross-slice need is composed in a
528
+ consuming hook that reads both stores; the FSD import direction
529
+ (`features → entities → shared`) still holds.
530
+ - A store holds **client** state only. If you are tempted to cache a request
531
+ result in it, that data belongs in TanStack Query.
532
+
533
+ ## Routes (`src/app`, Expo Router)
534
+
535
+ **`src/app` stays maximally empty.** Files under it only wire routes to
536
+ implementation — screen UI, business logic, state, and helpers live in
537
+ `~pages/*` and lower layers. No extra logic, no extra dependencies, no
538
+ components defined in place: anything beyond a thin wrapper or a navigator
539
+ declaration is a defect. When you notice logic accumulating in `src/app` (or
540
+ are asked to add some), flag it and propose where it belongs instead.
541
+
542
+ Canonical examples: `references/routes/` (read them before creating or editing
543
+ any `src/app` file):
544
+
545
+ - `references/routes/_app.tsx` + `share.tsx`, `guide.tsx` — the root stack:
546
+ `_app.tsx` holds the root Stack with `Stack.Protected` and the root
547
+ registrations of globally reachable routes; `share.tsx` / `guide.tsx` are
548
+ the same thin wrappers as the domain ones.
549
+ - `references/routes/(stack)/` — Stack navigator: `_layout.tsx` (component
550
+ named `Layout`, options via `useNavigationStackOptions()`) plus its screens:
551
+ `index.tsx` (post list), `details.tsx` (post details), a sheet modal
552
+ `share.tsx` registered with `sheetOptions`, and a WebView screen `guide.tsx`
553
+ registered with `webOptions`.
554
+ - `references/routes/(tabs)/` — NativeTabs navigator: `_layout.tsx` with
555
+ `useNavigationTabBarNativeOptions()` / `useNavigationTabBarOptions()` and
556
+ the `NavigationTabsNativeProvider` / `NavigationTabsProvider` pair; each tab
557
+ points to a group folder (`(post-list)/`, `(post-details)/`) holding that
558
+ tab's own screens.
559
+ - Route screen files (`index.tsx`, `details.tsx`, `[param].tsx`, …):
560
+ component named `Page`, renders a single root component imported from
561
+ `~pages/...`, nothing else.
562
+
563
+ ### Screen options
564
+
565
+ `useNavigationStackOptions()` returns four ready option objects and a
566
+ `createXxxOptions(overrides)` factory for each:
567
+
568
+ | Options | Factory | Use |
569
+ | --------------- | ----------------------- | ----------------------------------------------------- |
570
+ | `stackOptions` | `createStackOptions()` | the `screenOptions` of a `Stack` navigator |
571
+ | `screenOptions` | `createScreenOptions()` | a regular push screen |
572
+ | `sheetOptions` | `createSheetOptions()` | a sheet modal (`formSheet`, fit-to-contents, grabber) |
573
+ | `webOptions` | `createWebOptions()` | a WebView screen (full-height `formSheet`) |
574
+
575
+ Pass the ready object when no customization is needed
576
+ (`options={sheetOptions}`); reach for the factory only to override specific
577
+ options in place (`options={createStackOptions({ animation: 'none' })}`).
578
+
579
+ ### Rules
580
+
581
+ - `_layout.tsx` holds navigation and options only — never content markup.
582
+ - **Every screen is registered explicitly with its own `<Stack.Screen>` in its
583
+ stack's `_layout.tsx`** — with the options object its role calls for
584
+ (`screenOptions` for a push screen, `sheetOptions` for a modal, `webOptions`
585
+ for a web view). Expo Router auto-discovers a route file even without a
586
+ `<Stack.Screen>` line, so a missing registration does not break navigation —
587
+ it silently falls back to the navigator's default `stackOptions`, and the
588
+ screen quietly diverges from its siblings (wrong content background, no
589
+ animation/gesture config). That silent drift is the defect: a route file with
590
+ no matching `<Stack.Screen>` in its `_layout.tsx` is incomplete. The layout is
591
+ the full, readable census of the stack — every route file appears there once,
592
+ and every registration points at a real route file.
593
+ - **Registrations are sorted alphabetically by `name`**, in one flat list — the
594
+ same ordering as the route files on disk and the page barrel (no grouping by
595
+ sub-section, no blank-line blocks). The `_layout` then reads as a mirror of the
596
+ folder, so a screen is found the same way in both. The only splits allowed are
597
+ structural navigator branches (`Stack.Protected` guards in the root stack, the
598
+ `if`/`else` native-vs-JS tab bar) — sort alphabetically **within** each branch,
599
+ never regroup across one.
600
+ - Routes are named after the business domain: group folders `(post-list)`,
601
+ titles from the domain's translation namespace (`t('post.list.title')`,
602
+ `t('post.details.title')`).
603
+ - **A route segment is the screen's own name, not its sub-section.** The route
604
+ file (`tags.tsx`) and its `Stack.Screen name` (`'tags'`) carry only the
605
+ screen's own identity — the domain stack's `_layout.tsx` already expresses the
606
+ section, so `settings-tags.tsx` / `name={'settings-tags'}` is a defect. This
607
+ mirrors the page-naming rule under "Pages": the same screen is `post-tags/` in
608
+ `~pages/` and `tags` as a route. Links target that flat segment
609
+ (`/(post)/tags`), which is what keeps a screen movable between sub-menus
610
+ without touching its name.
611
+ - Route screen files never duplicate screen markup — only the thin wrapper.
612
+ - **Modals (sheets) and web screens are routes like any other.** There is no
613
+ global modal registry: the route is registered in the `_layout.tsx` of the
614
+ domain stack that owns its business logic, next to that domain's screens,
615
+ with `sheetOptions` (or `webOptions` for WebView screens). This keeps the
616
+ whole domain — screens, its modals, its web views — discoverable in one
617
+ layout. The sheet hides the native header, so no `title` is passed.
618
+ - In `~pages/` a modal or web screen is **not** marked in any way — no folder,
619
+ no name suffix; it is a regular page component of its domain. The only
620
+ difference is the root element: a `ModalContainer*` template
621
+ (`ModalContainerScroll` / `ModalContainerList` /
622
+ `ModalContainerSectionList`) for modals, `WebContainer` for web screens,
623
+ instead of `ScreenContainer*`.
624
+ - **A route that must open over any context** (push notification, deep link,
625
+ cross-domain call) — a modal, a web screen, or a regular screen — is
626
+ _additionally_ registered in the root stack in `_app.tsx`, inside
627
+ `Stack.Protected`, with the same thin route file and the same options object
628
+ — no local option overrides on either copy. The two routes are two
629
+ behaviors, not duplication: the domain route opens in its domain context,
630
+ the root route opens over whatever screen is current. The root URL is the
631
+ canonical entry for everything external (pushes, links, analytics);
632
+ in-domain flows navigate to the domain route.
633
+ - Both `Layout` and `Page` are default-exported at the end of the file
634
+ (Expo Router requirement, same export policy as everywhere).
635
+ - Special Expo Router files (`+not-found.tsx`, `+html.tsx`, …) follow their own
636
+ contract from the Expo docs.
637
+
638
+ ### Multi-screen flows: back vs close
639
+
640
+ A **flow** is a sequence of pushed screens that together complete one task
641
+ (enter phone → confirm code → success). Its screens split into two roles, and
642
+ the header reflects the role:
643
+
644
+ - **Every step except the last** carries a **back** control — an
645
+ `ScreenHeaderIconBack` in the header's **`left`** slot. It steps one screen
646
+ back (`navigation.back()`), letting the user revise the previous step.
647
+ - **The last screen** (the success/result step) carries a **close** control
648
+ instead — an `ScreenHeaderIconClose` in the header's **`right`** slot, and
649
+ **no** back on the left. From the end of a flow, stepping back into the
650
+ just-completed steps is wrong; the screen is terminal. Close dismisses the
651
+ whole flow at once, back to **the screen the flow started from** (its first
652
+ screen), via `navigation.dismissTo(firstScreenHref)`.
653
+
654
+ ```tsx
655
+ // last screen of a flow — close (right), no back (left)
656
+ <ScreenContainerScroll
657
+ screenHeader={<ScreenHeader right={<ScreenHeaderIconClose to={'/(profile)/security'} />} />}
658
+ >
659
+ ```
660
+
661
+ `ScreenHeaderIconClose` takes the flow's first-screen `to` href and collapses
662
+ the stack to it (without a `to` it exits to the stack root); pass the same href
663
+ the flow was launched from. When closing must also do flow cleanup (resetting a
664
+ wizard store), keep that in the screen's own handler rather than the shared
665
+ icon. Back on the left, close on the right — never both, never a back on a
666
+ terminal screen.
667
+
668
+ ## Pages (`src/pages`)
669
+
670
+ Canonical scaffold: `references/pages/post/` — a domain folder with four pages:
671
+ `post-list/` (with a styles hook), `post-details/` (without one), `post-share/`
672
+ (a modal page on `ModalContainerScroll`), and `post-guide/` (a web page on
673
+ `WebContainer`). Read it before creating or restructuring any page.
674
+
675
+ - **Every page is a folder, never a flat file.** A page lives at
676
+ `src/pages/<domain>/<domain>-<name>/` (kebab-case, prefixed with its domain:
677
+ `post-list`, not `list`). Markup, styles, and future page-local files all
678
+ live inside that folder — a bare `<domain>-<name>.tsx` next to the domain
679
+ barrel is a defect.
680
+ - **Name a screen from its domain and itself — never from the sub-section it
681
+ currently sits in.** The `<name>` part is the screen's own identity, and the
682
+ prefix is the top-level domain, not the intermediate menu/group the screen
683
+ happens to live under today. Screens migrate between sub-sections, and a name
684
+ that bakes in the grouping goes stale the moment they do. A screen that edits
685
+ a post's tags is `post-tags/` (component `PostTags`, route `tags`) whether it
686
+ is reached from a "settings" sub-menu or a "security" sub-menu — never
687
+ `post-settings-tags` / `PostSettingsTags`. The sub-section is expressed by
688
+ **where** the route is registered and **how** it is reached, not by the name.
689
+ When two screens would collide under this rule (e.g. a screen and its
690
+ confirmation modal both reducing to `post-delete`), disambiguate by their
691
+ distinct role — `post-delete` for the screen, `post-delete-confirm` for the
692
+ modal — never by re-introducing the sub-section prefix.
693
+ - Files inside the page folder:
694
+ - `<domain>-<name>.tsx` — the page component, named `<Domain><Name>`
695
+ (`PostList`), default-exported at the bottom;
696
+ - `index.ts` — explicit re-export:
697
+ `export { default as PostList } from './post-list'`;
698
+ - `<domain>-<name>.styles.ts` — appears together with the first piece of
699
+ visual logic, holding the `useXxxStyles` hook (same rules as components:
700
+ no inline styles, tokens only);
701
+ - `<domain>-<name>.types.ts` — only when the page actually has props;
702
+ pages normally take none (route params come via `useNavigationParams()`).
703
+ - The domain barrel `src/pages/<domain>/index.ts` contains only
704
+ `export * from './<domain>-<name>'` lines, sorted alphabetically — like
705
+ every other barrel.
706
+
707
+ ### The page owns the screen container
708
+
709
+ **Every page's root element is a screen-scaffold template — never anything
710
+ else.** The page's `return` is exactly one of:
711
+
712
+ - `ScreenContainerScroll` / `ScreenContainerList` / `ScreenContainerSectionList`
713
+ (`ScreenContainer*`) — a normal screen;
714
+ - `ModalContainerScroll` / `ModalContainerList` / `ModalContainerSectionList`
715
+ (`ModalContainer*`) — a modal/sheet page;
716
+ - `WebContainer` — a WebView page;
717
+ - `ScreenAuth` — the auth-screen scaffold (itself a `ScreenContainerScroll`
718
+ underneath), used only by auth pages.
719
+
720
+ A page whose root is a widget (`<SomeWidget />`), a feature, an entity, a raw
721
+ `View`, or a fragment (`<>…</>`) is a **defect**. Widgets, features, entities,
722
+ and shared UI are always composed _inside_ the container, never rendered as the
723
+ screen root:
724
+
725
+ ```tsx
726
+ // ✅ page owns the container; the widget is the inner content
727
+ const ProfileEdit = () => (
728
+ <ScreenContainerScroll screenHeader={<ScreenHeader left={<ScreenHeaderIconBack />} />}>
729
+ <ProfileEditForm />
730
+ </ScreenContainerScroll>
731
+ )
732
+
733
+ // ❌ widget is the screen root — the container is hidden inside the widget
734
+ const ProfileEdit = () => <ProfileEditForm />
735
+ ```
736
+
737
+ **The container and its chrome live in the page, not in a widget.** The
738
+ `ScreenContainer*` element — together with its `screenHeader` / `footer` /
739
+ `onRefresh` / list props — belongs to the page. A widget that renders its own
740
+ `ScreenContainer*` (owning the header, footer, refresh, or the `FlatList`
741
+ itself) is the same defect from the other side: it turns the widget _into_ the
742
+ screen and leaves the page an empty pass-through. Widgets render inner content
743
+ only.
744
+
745
+ When the header/footer/refresh are data-driven, the page calls the owning hook
746
+ and wires the container from it — passing what the inner widget needs down as
747
+ props, so there is a **single** hook instance. Never duplicate a hook that
748
+ holds local form state (fields would desync from the footer's submit); a hook
749
+ that is purely query/store-backed may be re-read anywhere, since the cache is
750
+ shared.
751
+
752
+ - Heavy markup belongs to widgets; the page composes them inside the container.
753
+
754
+ ### Pull-to-refresh
755
+
756
+ **Every screen that shows refetchable server data wires pull-to-refresh** — the
757
+ component that owns the data (the page, or the widget the page composes) passes
758
+ `onRefresh` to its `ScreenContainer*` root. See `handleRefresh` in
759
+ `references/pages/post/post-list/` and `references/hooks/use-posts.ts`.
760
+
761
+ - The handler follows the standard shape: `handleRefresh` in `useCallback` that
762
+ `await`s the query's `refetch`. When the page reads data through a domain
763
+ hook, that hook exposes `handleRefresh` alongside the data.
764
+ - The shared `RefreshControl` tracks its own spinner while the returned promise
765
+ is pending — the page never keeps an `isRefreshing` state and never renders a
766
+ `RefreshControl` manually.
767
+ - A screen composed of several queries refreshes **all** of them:
768
+ `await Promise.all([refetchA(), refetchB()])`.
769
+ - Out of scope: screens without server data (static menus, purely local forms)
770
+ and `ModalContainer*` modals — the modal templates deliberately do not
771
+ support pull-to-refresh.
772
+
773
+ ## Forms: keyboard UX
774
+
775
+ Every form with **two or more text inputs** ships the standard keyboard flow —
776
+ don't reinvent it per form:
777
+
778
+ - **autofocus** of the first field after the screen transition animation
779
+ finishes (the keyboard must not fight the native navigation animation);
780
+ - **Enter → next field** (focus moves, keyboard stays up);
781
+ - **Enter on the last field → submit** — the same handler as the form's main
782
+ button.
783
+
784
+ The primitives already exist: the `useFormFields` hook (`src/shared/hooks`),
785
+ the extended `Input` (props `returnKeyType` / `submitBehavior` / `onSubmit`),
786
+ and `useNavigationTransitionEnd`, which defers autofocus until the native
787
+ transition ends (`autoFocus`, `setTimeout`, and `InteractionManager` all fire
788
+ during the transition — only the navigation `transitionEnd` signal is
789
+ reliable).
790
+
791
+ ```tsx
792
+ const handleSubmit = useCallback(() => {
793
+ // the form's main action (request, navigation, …)
794
+ }, [])
795
+
796
+ const { getFieldProps } = useFormFields(2, { onSubmit: handleSubmit })
797
+
798
+ <Input {...getFieldProps(0)} value={email} onChange={setEmail} />
799
+ <Input {...getFieldProps(1)} value={password} onChange={setPassword} isSecure={true} />
800
+ <Button title={t('actions.sign_in')} type={'base'} onPress={handleSubmit} />
801
+ ```
802
+
803
+ - `getFieldProps(index)` hands the field `ref`, `shouldFocus`,
804
+ `returnKeyType`, `submitBehavior`, and `onSubmit`; the field stays fully
805
+ controlled via its own `value` / `onChange` / `label`.
806
+ - Spread `{...getFieldProps(index)}` **first** among the `Input` props so
807
+ explicit props can override the navigation ones.
808
+ - `index` is the visual order (0-based, top to bottom) of the **actually
809
+ rendered** fields; keep `fieldCount` and indices in sync with the render.
810
+ Dynamically shown fields are simpler left unwired.
811
+ - The submit button and the last field call **the same** handler.
812
+ - Autofocus is on by default; disable with `{ shouldAutoFocus: false }` when
813
+ the user should read the screen first.
814
+ - Non-`TextInput` fields (`input-select`, `input-phone`, `input-code`) don't
815
+ take these props — use their own focus/submit mechanisms.
816
+ - A single field needs no form hook: `shouldFocus={true}` directly, or, if
817
+ immediate focus fights the transition,
818
+ `useNavigationTransitionEnd(() => ref.current?.focus())`.
819
+
820
+ ## Translations
821
+
822
+ `src/shared/assets/translations/locales/` is **generated and gitignored —
823
+ never edit it by hand and never commit it.** The source of truth is the web
824
+ app: `../front/lang/<code>.json`, and **only the `mobile` section** of each
825
+ file. `scripts/transfer-translations.mjs` copies `JSON.parse(file).mobile`
826
+ into `.ts` modules; regenerate any time with **`npm run translations`** (also
827
+ runs on `postinstall`).
828
+
829
+ ### Hard rules
830
+
831
+ 1. **New translations go to Russian only** — `../front/lang/ru.json`, under
832
+ `mobile.*`, and nowhere else. Never add or translate the other languages;
833
+ those files are parser input.
834
+ 2. **Never edit anything outside the `mobile` property.** The rest of every
835
+ web `lang` file belongs to the web app — not our zone, under any
836
+ circumstance.
837
+ 3. **Do not edit existing web translations on your own.** Only when the user
838
+ explicitly asks, and always confirm the exact key + new text before
839
+ writing.
840
+ 4. **Reuse before adding.** Prefer an existing key (`actions.*`,
841
+ `elements.*`, an existing `screens.*`) over a new one. When a string
842
+ genuinely has no key, collect the proposed `key → Russian text` list and
843
+ get the user's confirmation before adding.
844
+ 5. **Regenerate after any web change** (`npm run translations`), then wire
845
+ the key(s) in code.
846
+
847
+ ### The `mobile.` prefix
848
+
849
+ The script strips the `mobile` wrapper: the key in code has **no `mobile.`
850
+ prefix**, the path in the web file (and in the change report) **has it**.
851
+ Web file: `mobile.screens.auth.sign_in.separator` → code:
852
+ `t('screens.auth.sign_in.separator')`.
853
+
854
+ ### Finding and structuring keys
855
+
856
+ Top-level domains under `mobile`: `screens.<feature>.<screen>.*` (copy scoped
857
+ to one screen), `navigation.*` (tab/section names), `actions.*` (reusable
858
+ button labels — check here first for buttons), `elements.*` (reusable UI
859
+ bits: `elements.fields.<field>.{title,placeholder,…}`, `elements.labels.*`,
860
+ `elements.modals.*`, …), `errors.*` / `validation.*`. Don't memorize keys —
861
+ grep `../front/lang/ru.json` for a distinctive substring of the Russian text.
862
+
863
+ ### Conventions
864
+
865
+ - Keys and interpolation variables are **snake_case**; interpolation is
866
+ i18next `{{var}}`: `t('…pending_card_title', { currency })`.
867
+ - Rich text (links/bold inside a sentence) uses angle-bracket tags rendered
868
+ by `LocalizationTranslate` (re-export of i18next `Trans`) with a
869
+ `components` map keyed by tag name (`<link-terms>…</link-terms>`).
870
+ - In code: `const t = useLocalizationText()`, then `t('actions.sign_in')`.
871
+
872
+ ### Change report — mandatory
873
+
874
+ After any task that changed the web files, end the answer with one block per
875
+ changed key, using the full web path (with `mobile.`):
876
+
877
+ ```
878
+ Ключ "mobile.screens.balance.sum"
879
+ Новый перевод: "Сумма, {{currency}}"
880
+ ```
881
+
882
+ ## Async
883
+
884
+ Asynchronous code uses `async/await` — `.then()` / `.catch()` / `.finally()`
885
+ chains are not used. Promise chains nest poorly, hide control flow, and split
886
+ error handling across callbacks; `await` reads top-to-bottom like sync code.
887
+
888
+ - Always `await` and wrap fallible calls in `try/catch` (use `catch {` without a
889
+ binding when the error is unused), never `.then(onOk, onErr)` or
890
+ `.catch(...)`.
891
+ - A sync callback that must return a value (an unsubscribe, a cleanup) but also
892
+ needs to kick off async work declares a local `async` helper and calls it —
893
+ the floating call is fine, no `void` operator needed:
894
+
895
+ ```ts
896
+ someListener((set) => {
897
+ const sync = async () => {
898
+ try {
899
+ set(await read())
900
+ } catch {
901
+ set(fallback)
902
+ }
903
+ }
904
+
905
+ sync()
906
+
907
+ return () => unsubscribe()
908
+ })
909
+ ```
910
+
911
+ - Run independent awaits concurrently with `await Promise.all([...])`, not
912
+ sequential `await`s.
913
+
914
+ ## Documentation and comments
915
+
916
+ JSDoc on the public surface is **mandatory** on every file; ordinary inline comments stay
917
+ discouraged.
918
+
919
+ **Language:** every JSDoc block is written in **Russian**. Code identifiers (names of
920
+ types, functions, props, constants) keep their original form — translate the prose, never
921
+ the symbols.
922
+
923
+ ### JSDoc — required on the public surface, in Russian
924
+
925
+ A `/** … */` block sits immediately above every declaration on a file's public /
926
+ top-level surface:
927
+
928
+ - exported and top-level functions, hooks, components;
929
+ - classes, and each of their methods and fields;
930
+ - exported and top-level types and constants.
931
+
932
+ ```ts
933
+ /** Форматирует сумму в локали пользователя; откатывается к сырому числу. */
934
+ const formatPrice = (amount: number, locale: Locale): string => { … }
935
+
936
+ /** Постраничный срез результатов плюс курсор на следующую страницу. */
937
+ type Page<Item> = {
938
+ /** Элементы текущей страницы. */
939
+ itemList: Item[]
940
+ /** Курсор следующей страницы; отсутствует на последней. */
941
+ nextCursor?: string
942
+ }
943
+ ```
944
+
945
+ - Document intent (what-for / why), not a restatement of the signature. Add `@param` /
946
+ `@returns` only when they carry information the types do not.
947
+ - A single-line `/** … */` is enough when one line says it; expand only when it earns it.
948
+ - Local variables inside a function body get **no** JSDoc.
949
+
950
+ ### Inline comments stay discouraged
951
+
952
+ Inside a function body, prefer renaming or extracting over a `//` explanation — the code
953
+ must read declaratively. Sanctioned non-JSDoc inline markers remain:
954
+
955
+ - Figma variant markers in typography styles (`/* big-title */`), kept solely so
956
+ variants are findable by search.
957
+ - `// auth` / `// no auth` markers on repository methods in the shared API layer
958
+ (written by the api skill) and the `// config` / `// response` section markers in
959
+ repository types files.
960
+
961
+ ## Quick self-check before finishing
962
+
963
+ - Arrays end with `List` (generic list components may keep `data: Item[]`);
964
+ booleans start with `is/can/did/has/should/with` (controlled selection
965
+ components keep `value?: boolean`)
966
+ - Dictionaries plural, counters `*Count`, design constants UPPER_SNAKE
967
+ - Files/folders kebab-case; exports collected at end of file; default export
968
+ only for components
969
+ - `@mediacubeco` utilities used via subpath imports; nothing re-implemented;
970
+ optional callbacks never called via `?.()`
971
+ - Visual logic in `useXxxStyles`; no inline styles; tokens, never hardcoded
972
+ visual values; theme and measurement hooks never called in the component
973
+ file; styles hook takes no arguments when it can (finite variants built
974
+ inside, picked by key), a context only for non-enumerable runtime values
975
+ - Colors by role with alpha steps; accent via `PRIMARY*`; paired contrast
976
+ tokens; radii and spacing from `SIZES` (computed `UPPER_SNAKE_CASE`
977
+ constants with JSDoc; cards at `5XL`+; rhythm table respected)
978
+ - Text only through the `Text` atom's `type` scale; a text skeleton is as tall
979
+ as the replaced type's line box (`fontSize` token × 1.2), never raw pixels;
980
+ `Glass`/`Blur` per the materials rules; shadows from `SHADOWS`
981
+ - Animation configs (when passed) only via `ANIMATIONS` tokens; `Touch`
982
+ feedback prop by role (`withRipple` / `withSpring` / `withHaptics`);
983
+ disabled via `DISABLED` tokens + `isDisabled`; selection UIs follow
984
+ `references/components/payout-speed-option/`
985
+ - New component: 4-file folder at the right atomic layer, explicit `index.ts`
986
+ re-exports, registered alphabetically in the layer barrel
987
+ - Every handler in `useCallback`; `useMemo`/`memo` only where expensive
988
+ - New hook: `use-<name>.ts` in the right `hooks/` folder, registered in its
989
+ barrel; multi-value hooks return one object, single-value hooks return the
990
+ value; no hand-written return types
991
+ - New store: Zustand per the State section — slice `model/` (or
992
+ `src/shared/lib/store/` for cross-cutting), selectors at call sites,
993
+ persist only what must survive a restart
994
+ - New page: own folder `src/pages/<domain>/<domain>-<name>/` with `index.ts`
995
+ (+ `.styles.ts` for visual logic); domain barrel alphabetical; no flat page
996
+ files
997
+ - Page root is a screen scaffold (`ScreenContainer*` / `ModalContainer*` /
998
+ `WebContainer` / `ScreenAuth`) — never a widget/feature/entity/`View`/fragment;
999
+ the container and its `screenHeader` / `footer` / `onRefresh` live in the page,
1000
+ never inside a widget; a data-driven container calls one hook instance (props
1001
+ down to the widget — no duplicating a local-state form hook)
1002
+ - Flow screens: back (`ScreenHeaderIconBack`, `left`) on every step but the last;
1003
+ the terminal screen uses close (`ScreenHeaderIconClose to={…}`, `right`,
1004
+ `dismissTo` the flow's first screen) with no back
1005
+ - `src/app` files stay thin wrappers (`Page` / `Layout`) — no logic, no extra
1006
+ dependencies; sheets via `sheetOptions`, web screens via `webOptions`
1007
+ - Screen with server data passes `onRefresh` (a `handleRefresh` awaiting
1008
+ `refetch`) to its `ScreenContainer*`; no manual `isRefreshing` state
1009
+ - Form with 2+ text inputs wired through `useFormFields` (autofocus after
1010
+ transition, Enter chain, last field submits)
1011
+ - Translations only in `../front/lang/ru.json` (namespace `mobile`, Russian
1012
+ only), then `npm run translations`; generated assets never edited by hand;
1013
+ change report with full `mobile.*` paths
1014
+ - JSX props braced and explicit; `&&` for single-branch rendering, ternary
1015
+ only for a real two-way choice; prop order respected
1016
+ - Object shapes via `type` (no `interface`); fixed constant sets via string
1017
+ `enum` (PascalCase members); constant lookup maps typed by `as const`, not an
1018
+ explicit `Record<…>` annotation
1019
+ - `ref` as a plain prop; never `forwardRef`
1020
+ - `async/await` with `try/catch` everywhere; no `.then()`/`.catch()` chains, no
1021
+ `void` operator
1022
+ - JSDoc (Russian) on every public/top-level declaration and every class member; intent
1023
+ over signature restatement
1024
+ - No explanatory inline `//` inside bodies (Figma `/* … */` markers and API-layer
1025
+ `// auth` / section markers excepted)