nastech-app 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1111) hide show
  1. package/.claude/agents/i18n-translator.md +119 -0
  2. package/.claude/settings.json +8 -0
  3. package/.eas/workflows/ota.yaml +9 -0
  4. package/.eas/workflows/preview.yaml +12 -0
  5. package/.easignore +12 -0
  6. package/.github/workflows/eas-build.yml +24 -0
  7. package/CHANGELOG.md +117 -0
  8. package/CLAUDE.md +413 -0
  9. package/GoogleService-Info.plist +30 -0
  10. package/LICENSE +21 -0
  11. package/NasTechapp.md +383 -0
  12. package/README.md +75 -0
  13. package/Stores.md +85 -0
  14. package/TERMS.md +83 -0
  15. package/app.config.js +153 -0
  16. package/babel.config.js +28 -0
  17. package/deploy/nastech-app.yaml +51 -0
  18. package/docs/marketing/README-creators.md +73 -0
  19. package/eas-build-post-install.sh +11 -0
  20. package/eas-build-pre-install.sh +27 -0
  21. package/eas.json +78 -0
  22. package/google-services.json +67 -0
  23. package/index.ts +3 -0
  24. package/logo.png +0 -0
  25. package/metro.config.js +54 -0
  26. package/nativewind-env.d.ts +1 -0
  27. package/package.json +233 -0
  28. package/patches/.keep +0 -0
  29. package/plugins/withEinkCompatibility.js +156 -0
  30. package/public/.well-known/apple-app-site-association +22 -0
  31. package/public/.well-known/assetlinks.json +12 -0
  32. package/public/canvaskit.wasm +0 -0
  33. package/public/favicon-active.ico +0 -0
  34. package/release-dev.sh +7 -0
  35. package/release-production.sh +3 -0
  36. package/release.cjs +160 -0
  37. package/sources/-session/SessionView.tsx +944 -0
  38. package/sources/-session/sessionOverlayNav.ts +34 -0
  39. package/sources/app/(app)/_layout.tsx +321 -0
  40. package/sources/app/(app)/artifacts/[id].tsx +279 -0
  41. package/sources/app/(app)/artifacts/edit/[id].tsx +318 -0
  42. package/sources/app/(app)/artifacts/index.tsx +264 -0
  43. package/sources/app/(app)/artifacts/new.tsx +219 -0
  44. package/sources/app/(app)/changelog.tsx +113 -0
  45. package/sources/app/(app)/dev/colors.tsx +197 -0
  46. package/sources/app/(app)/dev/device-info.tsx +183 -0
  47. package/sources/app/(app)/dev/expo-constants.tsx +394 -0
  48. package/sources/app/(app)/dev/index.tsx +400 -0
  49. package/sources/app/(app)/dev/input-styles.tsx +1951 -0
  50. package/sources/app/(app)/dev/inverted-list.tsx +295 -0
  51. package/sources/app/(app)/dev/list-demo.tsx +125 -0
  52. package/sources/app/(app)/dev/logs.tsx +160 -0
  53. package/sources/app/(app)/dev/messages-demo-data.ts +479 -0
  54. package/sources/app/(app)/dev/messages-demo.tsx +45 -0
  55. package/sources/app/(app)/dev/modal-demo.tsx +211 -0
  56. package/sources/app/(app)/dev/multi-text-input.tsx +224 -0
  57. package/sources/app/(app)/dev/purchases.tsx +228 -0
  58. package/sources/app/(app)/dev/qr-test.tsx +168 -0
  59. package/sources/app/(app)/dev/session-composer.tsx +812 -0
  60. package/sources/app/(app)/dev/shimmer-demo.tsx +275 -0
  61. package/sources/app/(app)/dev/tests.tsx +203 -0
  62. package/sources/app/(app)/dev/tools2.tsx +556 -0
  63. package/sources/app/(app)/dev/typography.tsx +177 -0
  64. package/sources/app/(app)/dev/unistyles-demo.tsx +376 -0
  65. package/sources/app/(app)/friends/index.tsx +167 -0
  66. package/sources/app/(app)/friends/search.tsx +232 -0
  67. package/sources/app/(app)/inbox/index.tsx +124 -0
  68. package/sources/app/(app)/index.tsx +264 -0
  69. package/sources/app/(app)/machine/[id].tsx +646 -0
  70. package/sources/app/(app)/new/index.tsx +1611 -0
  71. package/sources/app/(app)/restore/index.tsx +167 -0
  72. package/sources/app/(app)/restore/manual.tsx +138 -0
  73. package/sources/app/(app)/server.tsx +234 -0
  74. package/sources/app/(app)/session/[id]/file.tsx +527 -0
  75. package/sources/app/(app)/session/[id]/files.tsx +442 -0
  76. package/sources/app/(app)/session/[id]/info.tsx +655 -0
  77. package/sources/app/(app)/session/[id]/message/[messageId].tsx +125 -0
  78. package/sources/app/(app)/session/[id].tsx +10 -0
  79. package/sources/app/(app)/session/recent.tsx +270 -0
  80. package/sources/app/(app)/settings/account.tsx +600 -0
  81. package/sources/app/(app)/settings/agents.tsx +180 -0
  82. package/sources/app/(app)/settings/appearance.tsx +259 -0
  83. package/sources/app/(app)/settings/connect/claude.tsx +178 -0
  84. package/sources/app/(app)/settings/features.tsx +177 -0
  85. package/sources/app/(app)/settings/index.tsx +3 -0
  86. package/sources/app/(app)/settings/language.tsx +106 -0
  87. package/sources/app/(app)/settings/usage.tsx +11 -0
  88. package/sources/app/(app)/settings/voice/language.tsx +114 -0
  89. package/sources/app/(app)/settings/voice.tsx +274 -0
  90. package/sources/app/(app)/terminal/connect.tsx +241 -0
  91. package/sources/app/(app)/terminal/index.tsx +184 -0
  92. package/sources/app/(app)/text-selection.tsx +149 -0
  93. package/sources/app/(app)/user/[id].tsx +314 -0
  94. package/sources/app/+html.tsx +39 -0
  95. package/sources/app/_layout.tsx +402 -0
  96. package/sources/assets/animations/game.json +1 -0
  97. package/sources/assets/animations/owl.json +1 -0
  98. package/sources/assets/animations/popcorn.json +1 -0
  99. package/sources/assets/animations/robot.json +1 -0
  100. package/sources/assets/animations/sparkles.json +1 -0
  101. package/sources/assets/animations/stone.json +1 -0
  102. package/sources/assets/fonts/BricolageGrotesque-Bold.ttf +0 -0
  103. package/sources/assets/fonts/IBMPlexMono-Italic.ttf +0 -0
  104. package/sources/assets/fonts/IBMPlexMono-Regular.ttf +0 -0
  105. package/sources/assets/fonts/IBMPlexMono-SemiBold.ttf +0 -0
  106. package/sources/assets/fonts/IBMPlexSans-Italic.ttf +0 -0
  107. package/sources/assets/fonts/IBMPlexSans-Regular.ttf +0 -0
  108. package/sources/assets/fonts/IBMPlexSans-SemiBold.ttf +0 -0
  109. package/sources/assets/fonts/SpaceMono-Regular.ttf +0 -0
  110. package/sources/assets/images/brutalist/Abstract-1.png +0 -0
  111. package/sources/assets/images/brutalist/Abstract-10.png +0 -0
  112. package/sources/assets/images/brutalist/Abstract-100.png +0 -0
  113. package/sources/assets/images/brutalist/Abstract-101.png +0 -0
  114. package/sources/assets/images/brutalist/Abstract-102.png +0 -0
  115. package/sources/assets/images/brutalist/Abstract-103.png +0 -0
  116. package/sources/assets/images/brutalist/Abstract-104.png +0 -0
  117. package/sources/assets/images/brutalist/Abstract-105.png +0 -0
  118. package/sources/assets/images/brutalist/Abstract-106.png +0 -0
  119. package/sources/assets/images/brutalist/Abstract-107.png +0 -0
  120. package/sources/assets/images/brutalist/Abstract-108.png +0 -0
  121. package/sources/assets/images/brutalist/Abstract-109.png +0 -0
  122. package/sources/assets/images/brutalist/Abstract-11.png +0 -0
  123. package/sources/assets/images/brutalist/Abstract-110.png +0 -0
  124. package/sources/assets/images/brutalist/Abstract-111.png +0 -0
  125. package/sources/assets/images/brutalist/Abstract-112.png +0 -0
  126. package/sources/assets/images/brutalist/Abstract-113.png +0 -0
  127. package/sources/assets/images/brutalist/Abstract-114.png +0 -0
  128. package/sources/assets/images/brutalist/Abstract-115.png +0 -0
  129. package/sources/assets/images/brutalist/Abstract-116.png +0 -0
  130. package/sources/assets/images/brutalist/Abstract-117.png +0 -0
  131. package/sources/assets/images/brutalist/Abstract-118.png +0 -0
  132. package/sources/assets/images/brutalist/Abstract-119.png +0 -0
  133. package/sources/assets/images/brutalist/Abstract-12.png +0 -0
  134. package/sources/assets/images/brutalist/Abstract-120.png +0 -0
  135. package/sources/assets/images/brutalist/Abstract-121.png +0 -0
  136. package/sources/assets/images/brutalist/Abstract-122.png +0 -0
  137. package/sources/assets/images/brutalist/Abstract-123.png +0 -0
  138. package/sources/assets/images/brutalist/Abstract-124.png +0 -0
  139. package/sources/assets/images/brutalist/Abstract-125.png +0 -0
  140. package/sources/assets/images/brutalist/Abstract-126.png +0 -0
  141. package/sources/assets/images/brutalist/Abstract-127.png +0 -0
  142. package/sources/assets/images/brutalist/Abstract-128.png +0 -0
  143. package/sources/assets/images/brutalist/Abstract-129.png +0 -0
  144. package/sources/assets/images/brutalist/Abstract-13.png +0 -0
  145. package/sources/assets/images/brutalist/Abstract-130.png +0 -0
  146. package/sources/assets/images/brutalist/Abstract-131.png +0 -0
  147. package/sources/assets/images/brutalist/Abstract-132.png +0 -0
  148. package/sources/assets/images/brutalist/Abstract-133.png +0 -0
  149. package/sources/assets/images/brutalist/Abstract-134.png +0 -0
  150. package/sources/assets/images/brutalist/Abstract-135.png +0 -0
  151. package/sources/assets/images/brutalist/Abstract-136.png +0 -0
  152. package/sources/assets/images/brutalist/Abstract-137.png +0 -0
  153. package/sources/assets/images/brutalist/Abstract-138.png +0 -0
  154. package/sources/assets/images/brutalist/Abstract-139.png +0 -0
  155. package/sources/assets/images/brutalist/Abstract-14.png +0 -0
  156. package/sources/assets/images/brutalist/Abstract-140.png +0 -0
  157. package/sources/assets/images/brutalist/Abstract-141.png +0 -0
  158. package/sources/assets/images/brutalist/Abstract-142.png +0 -0
  159. package/sources/assets/images/brutalist/Abstract-143.png +0 -0
  160. package/sources/assets/images/brutalist/Abstract-144.png +0 -0
  161. package/sources/assets/images/brutalist/Abstract-145.png +0 -0
  162. package/sources/assets/images/brutalist/Abstract-146.png +0 -0
  163. package/sources/assets/images/brutalist/Abstract-147.png +0 -0
  164. package/sources/assets/images/brutalist/Abstract-148.png +0 -0
  165. package/sources/assets/images/brutalist/Abstract-149.png +0 -0
  166. package/sources/assets/images/brutalist/Abstract-15.png +0 -0
  167. package/sources/assets/images/brutalist/Abstract-150.png +0 -0
  168. package/sources/assets/images/brutalist/Abstract-151.png +0 -0
  169. package/sources/assets/images/brutalist/Abstract-152.png +0 -0
  170. package/sources/assets/images/brutalist/Abstract-153.png +0 -0
  171. package/sources/assets/images/brutalist/Abstract-154.png +0 -0
  172. package/sources/assets/images/brutalist/Abstract-155.png +0 -0
  173. package/sources/assets/images/brutalist/Abstract-156.png +0 -0
  174. package/sources/assets/images/brutalist/Abstract-157.png +0 -0
  175. package/sources/assets/images/brutalist/Abstract-158.png +0 -0
  176. package/sources/assets/images/brutalist/Abstract-159.png +0 -0
  177. package/sources/assets/images/brutalist/Abstract-16.png +0 -0
  178. package/sources/assets/images/brutalist/Abstract-160.png +0 -0
  179. package/sources/assets/images/brutalist/Abstract-161.png +0 -0
  180. package/sources/assets/images/brutalist/Abstract-162.png +0 -0
  181. package/sources/assets/images/brutalist/Abstract-163.png +0 -0
  182. package/sources/assets/images/brutalist/Abstract-164.png +0 -0
  183. package/sources/assets/images/brutalist/Abstract-165.png +0 -0
  184. package/sources/assets/images/brutalist/Abstract-166.png +0 -0
  185. package/sources/assets/images/brutalist/Abstract-167.png +0 -0
  186. package/sources/assets/images/brutalist/Abstract-168.png +0 -0
  187. package/sources/assets/images/brutalist/Abstract-169.png +0 -0
  188. package/sources/assets/images/brutalist/Abstract-17.png +0 -0
  189. package/sources/assets/images/brutalist/Abstract-170.png +0 -0
  190. package/sources/assets/images/brutalist/Abstract-171.png +0 -0
  191. package/sources/assets/images/brutalist/Abstract-172.png +0 -0
  192. package/sources/assets/images/brutalist/Abstract-173.png +0 -0
  193. package/sources/assets/images/brutalist/Abstract-174.png +0 -0
  194. package/sources/assets/images/brutalist/Abstract-175.png +0 -0
  195. package/sources/assets/images/brutalist/Abstract-176.png +0 -0
  196. package/sources/assets/images/brutalist/Abstract-177.png +0 -0
  197. package/sources/assets/images/brutalist/Abstract-178.png +0 -0
  198. package/sources/assets/images/brutalist/Abstract-179.png +0 -0
  199. package/sources/assets/images/brutalist/Abstract-18.png +0 -0
  200. package/sources/assets/images/brutalist/Abstract-180.png +0 -0
  201. package/sources/assets/images/brutalist/Abstract-181.png +0 -0
  202. package/sources/assets/images/brutalist/Abstract-182.png +0 -0
  203. package/sources/assets/images/brutalist/Abstract-183.png +0 -0
  204. package/sources/assets/images/brutalist/Abstract-184.png +0 -0
  205. package/sources/assets/images/brutalist/Abstract-185.png +0 -0
  206. package/sources/assets/images/brutalist/Abstract-186.png +0 -0
  207. package/sources/assets/images/brutalist/Abstract-187.png +0 -0
  208. package/sources/assets/images/brutalist/Abstract-188.png +0 -0
  209. package/sources/assets/images/brutalist/Abstract-189.png +0 -0
  210. package/sources/assets/images/brutalist/Abstract-19.png +0 -0
  211. package/sources/assets/images/brutalist/Abstract-190.png +0 -0
  212. package/sources/assets/images/brutalist/Abstract-191.png +0 -0
  213. package/sources/assets/images/brutalist/Abstract-192.png +0 -0
  214. package/sources/assets/images/brutalist/Abstract-193.png +0 -0
  215. package/sources/assets/images/brutalist/Abstract-194.png +0 -0
  216. package/sources/assets/images/brutalist/Abstract-195.png +0 -0
  217. package/sources/assets/images/brutalist/Abstract-196.png +0 -0
  218. package/sources/assets/images/brutalist/Abstract-197.png +0 -0
  219. package/sources/assets/images/brutalist/Abstract-198.png +0 -0
  220. package/sources/assets/images/brutalist/Abstract-199.png +0 -0
  221. package/sources/assets/images/brutalist/Abstract-2.png +0 -0
  222. package/sources/assets/images/brutalist/Abstract-20.png +0 -0
  223. package/sources/assets/images/brutalist/Abstract-200.png +0 -0
  224. package/sources/assets/images/brutalist/Abstract-201.png +0 -0
  225. package/sources/assets/images/brutalist/Abstract-202.png +0 -0
  226. package/sources/assets/images/brutalist/Abstract-203.png +0 -0
  227. package/sources/assets/images/brutalist/Abstract-204.png +0 -0
  228. package/sources/assets/images/brutalist/Abstract-205.png +0 -0
  229. package/sources/assets/images/brutalist/Abstract-206.png +0 -0
  230. package/sources/assets/images/brutalist/Abstract-207.png +0 -0
  231. package/sources/assets/images/brutalist/Abstract-208.png +0 -0
  232. package/sources/assets/images/brutalist/Abstract-209.png +0 -0
  233. package/sources/assets/images/brutalist/Abstract-21.png +0 -0
  234. package/sources/assets/images/brutalist/Abstract-210.png +0 -0
  235. package/sources/assets/images/brutalist/Abstract-211.png +0 -0
  236. package/sources/assets/images/brutalist/Abstract-212.png +0 -0
  237. package/sources/assets/images/brutalist/Abstract-213.png +0 -0
  238. package/sources/assets/images/brutalist/Abstract-214.png +0 -0
  239. package/sources/assets/images/brutalist/Abstract-215.png +0 -0
  240. package/sources/assets/images/brutalist/Abstract-216.png +0 -0
  241. package/sources/assets/images/brutalist/Abstract-217.png +0 -0
  242. package/sources/assets/images/brutalist/Abstract-218.png +0 -0
  243. package/sources/assets/images/brutalist/Abstract-219.png +0 -0
  244. package/sources/assets/images/brutalist/Abstract-22.png +0 -0
  245. package/sources/assets/images/brutalist/Abstract-220.png +0 -0
  246. package/sources/assets/images/brutalist/Abstract-221.png +0 -0
  247. package/sources/assets/images/brutalist/Abstract-222.png +0 -0
  248. package/sources/assets/images/brutalist/Abstract-223.png +0 -0
  249. package/sources/assets/images/brutalist/Abstract-224.png +0 -0
  250. package/sources/assets/images/brutalist/Abstract-225.png +0 -0
  251. package/sources/assets/images/brutalist/Abstract-226.png +0 -0
  252. package/sources/assets/images/brutalist/Abstract-227.png +0 -0
  253. package/sources/assets/images/brutalist/Abstract-228.png +0 -0
  254. package/sources/assets/images/brutalist/Abstract-229.png +0 -0
  255. package/sources/assets/images/brutalist/Abstract-23.png +0 -0
  256. package/sources/assets/images/brutalist/Abstract-230.png +0 -0
  257. package/sources/assets/images/brutalist/Abstract-231.png +0 -0
  258. package/sources/assets/images/brutalist/Abstract-232.png +0 -0
  259. package/sources/assets/images/brutalist/Abstract-233.png +0 -0
  260. package/sources/assets/images/brutalist/Abstract-234.png +0 -0
  261. package/sources/assets/images/brutalist/Abstract-235.png +0 -0
  262. package/sources/assets/images/brutalist/Abstract-236.png +0 -0
  263. package/sources/assets/images/brutalist/Abstract-237.png +0 -0
  264. package/sources/assets/images/brutalist/Abstract-238.png +0 -0
  265. package/sources/assets/images/brutalist/Abstract-239.png +0 -0
  266. package/sources/assets/images/brutalist/Abstract-24.png +0 -0
  267. package/sources/assets/images/brutalist/Abstract-240.png +0 -0
  268. package/sources/assets/images/brutalist/Abstract-241.png +0 -0
  269. package/sources/assets/images/brutalist/Abstract-242.png +0 -0
  270. package/sources/assets/images/brutalist/Abstract-243.png +0 -0
  271. package/sources/assets/images/brutalist/Abstract-244.png +0 -0
  272. package/sources/assets/images/brutalist/Abstract-245.png +0 -0
  273. package/sources/assets/images/brutalist/Abstract-246.png +0 -0
  274. package/sources/assets/images/brutalist/Abstract-247.png +0 -0
  275. package/sources/assets/images/brutalist/Abstract-248.png +0 -0
  276. package/sources/assets/images/brutalist/Abstract-249.png +0 -0
  277. package/sources/assets/images/brutalist/Abstract-25.png +0 -0
  278. package/sources/assets/images/brutalist/Abstract-250.png +0 -0
  279. package/sources/assets/images/brutalist/Abstract-251.png +0 -0
  280. package/sources/assets/images/brutalist/Abstract-252.png +0 -0
  281. package/sources/assets/images/brutalist/Abstract-253.png +0 -0
  282. package/sources/assets/images/brutalist/Abstract-254.png +0 -0
  283. package/sources/assets/images/brutalist/Abstract-255.png +0 -0
  284. package/sources/assets/images/brutalist/Abstract-256.png +0 -0
  285. package/sources/assets/images/brutalist/Abstract-257.png +0 -0
  286. package/sources/assets/images/brutalist/Abstract-258.png +0 -0
  287. package/sources/assets/images/brutalist/Abstract-259.png +0 -0
  288. package/sources/assets/images/brutalist/Abstract-26.png +0 -0
  289. package/sources/assets/images/brutalist/Abstract-260.png +0 -0
  290. package/sources/assets/images/brutalist/Abstract-261.png +0 -0
  291. package/sources/assets/images/brutalist/Abstract-262.png +0 -0
  292. package/sources/assets/images/brutalist/Abstract-27.png +0 -0
  293. package/sources/assets/images/brutalist/Abstract-28.png +0 -0
  294. package/sources/assets/images/brutalist/Abstract-29.png +0 -0
  295. package/sources/assets/images/brutalist/Abstract-3.png +0 -0
  296. package/sources/assets/images/brutalist/Abstract-30.png +0 -0
  297. package/sources/assets/images/brutalist/Abstract-31.png +0 -0
  298. package/sources/assets/images/brutalist/Abstract-32.png +0 -0
  299. package/sources/assets/images/brutalist/Abstract-33.png +0 -0
  300. package/sources/assets/images/brutalist/Abstract-34.png +0 -0
  301. package/sources/assets/images/brutalist/Abstract-35.png +0 -0
  302. package/sources/assets/images/brutalist/Abstract-36.png +0 -0
  303. package/sources/assets/images/brutalist/Abstract-37.png +0 -0
  304. package/sources/assets/images/brutalist/Abstract-38.png +0 -0
  305. package/sources/assets/images/brutalist/Abstract-39.png +0 -0
  306. package/sources/assets/images/brutalist/Abstract-4.png +0 -0
  307. package/sources/assets/images/brutalist/Abstract-40.png +0 -0
  308. package/sources/assets/images/brutalist/Abstract-41.png +0 -0
  309. package/sources/assets/images/brutalist/Abstract-42.png +0 -0
  310. package/sources/assets/images/brutalist/Abstract-43.png +0 -0
  311. package/sources/assets/images/brutalist/Abstract-44.png +0 -0
  312. package/sources/assets/images/brutalist/Abstract-45.png +0 -0
  313. package/sources/assets/images/brutalist/Abstract-46.png +0 -0
  314. package/sources/assets/images/brutalist/Abstract-47.png +0 -0
  315. package/sources/assets/images/brutalist/Abstract-48.png +0 -0
  316. package/sources/assets/images/brutalist/Abstract-49.png +0 -0
  317. package/sources/assets/images/brutalist/Abstract-5.png +0 -0
  318. package/sources/assets/images/brutalist/Abstract-50.png +0 -0
  319. package/sources/assets/images/brutalist/Abstract-51.png +0 -0
  320. package/sources/assets/images/brutalist/Abstract-52.png +0 -0
  321. package/sources/assets/images/brutalist/Abstract-53.png +0 -0
  322. package/sources/assets/images/brutalist/Abstract-54.png +0 -0
  323. package/sources/assets/images/brutalist/Abstract-55.png +0 -0
  324. package/sources/assets/images/brutalist/Abstract-56.png +0 -0
  325. package/sources/assets/images/brutalist/Abstract-57.png +0 -0
  326. package/sources/assets/images/brutalist/Abstract-58.png +0 -0
  327. package/sources/assets/images/brutalist/Abstract-59.png +0 -0
  328. package/sources/assets/images/brutalist/Abstract-6.png +0 -0
  329. package/sources/assets/images/brutalist/Abstract-60.png +0 -0
  330. package/sources/assets/images/brutalist/Abstract-61.png +0 -0
  331. package/sources/assets/images/brutalist/Abstract-62.png +0 -0
  332. package/sources/assets/images/brutalist/Abstract-63.png +0 -0
  333. package/sources/assets/images/brutalist/Abstract-64.png +0 -0
  334. package/sources/assets/images/brutalist/Abstract-65.png +0 -0
  335. package/sources/assets/images/brutalist/Abstract-66.png +0 -0
  336. package/sources/assets/images/brutalist/Abstract-67.png +0 -0
  337. package/sources/assets/images/brutalist/Abstract-68.png +0 -0
  338. package/sources/assets/images/brutalist/Abstract-69.png +0 -0
  339. package/sources/assets/images/brutalist/Abstract-7.png +0 -0
  340. package/sources/assets/images/brutalist/Abstract-70.png +0 -0
  341. package/sources/assets/images/brutalist/Abstract-71.png +0 -0
  342. package/sources/assets/images/brutalist/Abstract-72.png +0 -0
  343. package/sources/assets/images/brutalist/Abstract-73.png +0 -0
  344. package/sources/assets/images/brutalist/Abstract-74.png +0 -0
  345. package/sources/assets/images/brutalist/Abstract-75.png +0 -0
  346. package/sources/assets/images/brutalist/Abstract-76.png +0 -0
  347. package/sources/assets/images/brutalist/Abstract-77.png +0 -0
  348. package/sources/assets/images/brutalist/Abstract-78.png +0 -0
  349. package/sources/assets/images/brutalist/Abstract-79.png +0 -0
  350. package/sources/assets/images/brutalist/Abstract-8.png +0 -0
  351. package/sources/assets/images/brutalist/Abstract-80.png +0 -0
  352. package/sources/assets/images/brutalist/Abstract-81.png +0 -0
  353. package/sources/assets/images/brutalist/Abstract-82.png +0 -0
  354. package/sources/assets/images/brutalist/Abstract-83.png +0 -0
  355. package/sources/assets/images/brutalist/Abstract-84.png +0 -0
  356. package/sources/assets/images/brutalist/Abstract-85.png +0 -0
  357. package/sources/assets/images/brutalist/Abstract-86.png +0 -0
  358. package/sources/assets/images/brutalist/Abstract-87.png +0 -0
  359. package/sources/assets/images/brutalist/Abstract-88.png +0 -0
  360. package/sources/assets/images/brutalist/Abstract-89.png +0 -0
  361. package/sources/assets/images/brutalist/Abstract-9.png +0 -0
  362. package/sources/assets/images/brutalist/Abstract-90.png +0 -0
  363. package/sources/assets/images/brutalist/Abstract-91.png +0 -0
  364. package/sources/assets/images/brutalist/Abstract-92.png +0 -0
  365. package/sources/assets/images/brutalist/Abstract-93.png +0 -0
  366. package/sources/assets/images/brutalist/Abstract-94.png +0 -0
  367. package/sources/assets/images/brutalist/Abstract-95.png +0 -0
  368. package/sources/assets/images/brutalist/Abstract-96.png +0 -0
  369. package/sources/assets/images/brutalist/Abstract-97.png +0 -0
  370. package/sources/assets/images/brutalist/Abstract-98.png +0 -0
  371. package/sources/assets/images/brutalist/Abstract-99.png +0 -0
  372. package/sources/assets/images/brutalist/Bauhaus-1.png +0 -0
  373. package/sources/assets/images/brutalist/Bauhaus-10.png +0 -0
  374. package/sources/assets/images/brutalist/Bauhaus-11.png +0 -0
  375. package/sources/assets/images/brutalist/Bauhaus-12.png +0 -0
  376. package/sources/assets/images/brutalist/Bauhaus-13.png +0 -0
  377. package/sources/assets/images/brutalist/Bauhaus-14.png +0 -0
  378. package/sources/assets/images/brutalist/Bauhaus-15.png +0 -0
  379. package/sources/assets/images/brutalist/Bauhaus-16.png +0 -0
  380. package/sources/assets/images/brutalist/Bauhaus-17.png +0 -0
  381. package/sources/assets/images/brutalist/Bauhaus-18.png +0 -0
  382. package/sources/assets/images/brutalist/Bauhaus-19.png +0 -0
  383. package/sources/assets/images/brutalist/Bauhaus-2.png +0 -0
  384. package/sources/assets/images/brutalist/Bauhaus-20.png +0 -0
  385. package/sources/assets/images/brutalist/Bauhaus-21.png +0 -0
  386. package/sources/assets/images/brutalist/Bauhaus-22.png +0 -0
  387. package/sources/assets/images/brutalist/Bauhaus-23.png +0 -0
  388. package/sources/assets/images/brutalist/Bauhaus-24.png +0 -0
  389. package/sources/assets/images/brutalist/Bauhaus-25.png +0 -0
  390. package/sources/assets/images/brutalist/Bauhaus-26.png +0 -0
  391. package/sources/assets/images/brutalist/Bauhaus-27.png +0 -0
  392. package/sources/assets/images/brutalist/Bauhaus-28.png +0 -0
  393. package/sources/assets/images/brutalist/Bauhaus-29.png +0 -0
  394. package/sources/assets/images/brutalist/Bauhaus-3.png +0 -0
  395. package/sources/assets/images/brutalist/Bauhaus-30.png +0 -0
  396. package/sources/assets/images/brutalist/Bauhaus-31.png +0 -0
  397. package/sources/assets/images/brutalist/Bauhaus-32.png +0 -0
  398. package/sources/assets/images/brutalist/Bauhaus-33.png +0 -0
  399. package/sources/assets/images/brutalist/Bauhaus-34.png +0 -0
  400. package/sources/assets/images/brutalist/Bauhaus-35.png +0 -0
  401. package/sources/assets/images/brutalist/Bauhaus-36.png +0 -0
  402. package/sources/assets/images/brutalist/Bauhaus-37.png +0 -0
  403. package/sources/assets/images/brutalist/Bauhaus-38.png +0 -0
  404. package/sources/assets/images/brutalist/Bauhaus-39.png +0 -0
  405. package/sources/assets/images/brutalist/Bauhaus-4.png +0 -0
  406. package/sources/assets/images/brutalist/Bauhaus-40.png +0 -0
  407. package/sources/assets/images/brutalist/Bauhaus-5.png +0 -0
  408. package/sources/assets/images/brutalist/Bauhaus-6.png +0 -0
  409. package/sources/assets/images/brutalist/Bauhaus-7.png +0 -0
  410. package/sources/assets/images/brutalist/Bauhaus-8.png +0 -0
  411. package/sources/assets/images/brutalist/Bauhaus-9.png +0 -0
  412. package/sources/assets/images/brutalist/Brutalism-1.png +0 -0
  413. package/sources/assets/images/brutalist/Brutalism-10.png +0 -0
  414. package/sources/assets/images/brutalist/Brutalism-100.png +0 -0
  415. package/sources/assets/images/brutalist/Brutalism-101.png +0 -0
  416. package/sources/assets/images/brutalist/Brutalism-102.png +0 -0
  417. package/sources/assets/images/brutalist/Brutalism-103.png +0 -0
  418. package/sources/assets/images/brutalist/Brutalism-104.png +0 -0
  419. package/sources/assets/images/brutalist/Brutalism-105.png +0 -0
  420. package/sources/assets/images/brutalist/Brutalism-106.png +0 -0
  421. package/sources/assets/images/brutalist/Brutalism-107.png +0 -0
  422. package/sources/assets/images/brutalist/Brutalism-108.png +0 -0
  423. package/sources/assets/images/brutalist/Brutalism-109.png +0 -0
  424. package/sources/assets/images/brutalist/Brutalism-11.png +0 -0
  425. package/sources/assets/images/brutalist/Brutalism-110.png +0 -0
  426. package/sources/assets/images/brutalist/Brutalism-111.png +0 -0
  427. package/sources/assets/images/brutalist/Brutalism-112.png +0 -0
  428. package/sources/assets/images/brutalist/Brutalism-113.png +0 -0
  429. package/sources/assets/images/brutalist/Brutalism-114.png +0 -0
  430. package/sources/assets/images/brutalist/Brutalism-115.png +0 -0
  431. package/sources/assets/images/brutalist/Brutalism-116.png +0 -0
  432. package/sources/assets/images/brutalist/Brutalism-117.png +0 -0
  433. package/sources/assets/images/brutalist/Brutalism-118.png +0 -0
  434. package/sources/assets/images/brutalist/Brutalism-12.png +0 -0
  435. package/sources/assets/images/brutalist/Brutalism-13.png +0 -0
  436. package/sources/assets/images/brutalist/Brutalism-14.png +0 -0
  437. package/sources/assets/images/brutalist/Brutalism-15.png +0 -0
  438. package/sources/assets/images/brutalist/Brutalism-16.png +0 -0
  439. package/sources/assets/images/brutalist/Brutalism-17.png +0 -0
  440. package/sources/assets/images/brutalist/Brutalism-18.png +0 -0
  441. package/sources/assets/images/brutalist/Brutalism-19.png +0 -0
  442. package/sources/assets/images/brutalist/Brutalism-2.png +0 -0
  443. package/sources/assets/images/brutalist/Brutalism-20.png +0 -0
  444. package/sources/assets/images/brutalist/Brutalism-21.png +0 -0
  445. package/sources/assets/images/brutalist/Brutalism-22.png +0 -0
  446. package/sources/assets/images/brutalist/Brutalism-23.png +0 -0
  447. package/sources/assets/images/brutalist/Brutalism-24.png +0 -0
  448. package/sources/assets/images/brutalist/Brutalism-25.png +0 -0
  449. package/sources/assets/images/brutalist/Brutalism-26.png +0 -0
  450. package/sources/assets/images/brutalist/Brutalism-27.png +0 -0
  451. package/sources/assets/images/brutalist/Brutalism-28.png +0 -0
  452. package/sources/assets/images/brutalist/Brutalism-29.png +0 -0
  453. package/sources/assets/images/brutalist/Brutalism-3.png +0 -0
  454. package/sources/assets/images/brutalist/Brutalism-30.png +0 -0
  455. package/sources/assets/images/brutalist/Brutalism-31.png +0 -0
  456. package/sources/assets/images/brutalist/Brutalism-32.png +0 -0
  457. package/sources/assets/images/brutalist/Brutalism-33.png +0 -0
  458. package/sources/assets/images/brutalist/Brutalism-34.png +0 -0
  459. package/sources/assets/images/brutalist/Brutalism-35.png +0 -0
  460. package/sources/assets/images/brutalist/Brutalism-36.png +0 -0
  461. package/sources/assets/images/brutalist/Brutalism-37.png +0 -0
  462. package/sources/assets/images/brutalist/Brutalism-38.png +0 -0
  463. package/sources/assets/images/brutalist/Brutalism-39.png +0 -0
  464. package/sources/assets/images/brutalist/Brutalism-4.png +0 -0
  465. package/sources/assets/images/brutalist/Brutalism-40.png +0 -0
  466. package/sources/assets/images/brutalist/Brutalism-41.png +0 -0
  467. package/sources/assets/images/brutalist/Brutalism-42.png +0 -0
  468. package/sources/assets/images/brutalist/Brutalism-43.png +0 -0
  469. package/sources/assets/images/brutalist/Brutalism-44.png +0 -0
  470. package/sources/assets/images/brutalist/Brutalism-45.png +0 -0
  471. package/sources/assets/images/brutalist/Brutalism-46.png +0 -0
  472. package/sources/assets/images/brutalist/Brutalism-47.png +0 -0
  473. package/sources/assets/images/brutalist/Brutalism-48.png +0 -0
  474. package/sources/assets/images/brutalist/Brutalism-49.png +0 -0
  475. package/sources/assets/images/brutalist/Brutalism-5.png +0 -0
  476. package/sources/assets/images/brutalist/Brutalism-50.png +0 -0
  477. package/sources/assets/images/brutalist/Brutalism-51.png +0 -0
  478. package/sources/assets/images/brutalist/Brutalism-52.png +0 -0
  479. package/sources/assets/images/brutalist/Brutalism-53.png +0 -0
  480. package/sources/assets/images/brutalist/Brutalism-54.png +0 -0
  481. package/sources/assets/images/brutalist/Brutalism-55.png +0 -0
  482. package/sources/assets/images/brutalist/Brutalism-56.png +0 -0
  483. package/sources/assets/images/brutalist/Brutalism-57.png +0 -0
  484. package/sources/assets/images/brutalist/Brutalism-58.png +0 -0
  485. package/sources/assets/images/brutalist/Brutalism-59.png +0 -0
  486. package/sources/assets/images/brutalist/Brutalism-6.png +0 -0
  487. package/sources/assets/images/brutalist/Brutalism-60.png +0 -0
  488. package/sources/assets/images/brutalist/Brutalism-61.png +0 -0
  489. package/sources/assets/images/brutalist/Brutalism-62.png +0 -0
  490. package/sources/assets/images/brutalist/Brutalism-63.png +0 -0
  491. package/sources/assets/images/brutalist/Brutalism-64.png +0 -0
  492. package/sources/assets/images/brutalist/Brutalism-65.png +0 -0
  493. package/sources/assets/images/brutalist/Brutalism-66.png +0 -0
  494. package/sources/assets/images/brutalist/Brutalism-67.png +0 -0
  495. package/sources/assets/images/brutalist/Brutalism-68.png +0 -0
  496. package/sources/assets/images/brutalist/Brutalism-69.png +0 -0
  497. package/sources/assets/images/brutalist/Brutalism-7.png +0 -0
  498. package/sources/assets/images/brutalist/Brutalism-70.png +0 -0
  499. package/sources/assets/images/brutalist/Brutalism-71.png +0 -0
  500. package/sources/assets/images/brutalist/Brutalism-72.png +0 -0
  501. package/sources/assets/images/brutalist/Brutalism-73.png +0 -0
  502. package/sources/assets/images/brutalist/Brutalism-74.png +0 -0
  503. package/sources/assets/images/brutalist/Brutalism-75.png +0 -0
  504. package/sources/assets/images/brutalist/Brutalism-76.png +0 -0
  505. package/sources/assets/images/brutalist/Brutalism-77.png +0 -0
  506. package/sources/assets/images/brutalist/Brutalism-78.png +0 -0
  507. package/sources/assets/images/brutalist/Brutalism-79.png +0 -0
  508. package/sources/assets/images/brutalist/Brutalism-8.png +0 -0
  509. package/sources/assets/images/brutalist/Brutalism-80.png +0 -0
  510. package/sources/assets/images/brutalist/Brutalism-81.png +0 -0
  511. package/sources/assets/images/brutalist/Brutalism-82.png +0 -0
  512. package/sources/assets/images/brutalist/Brutalism-83.png +0 -0
  513. package/sources/assets/images/brutalist/Brutalism-84.png +0 -0
  514. package/sources/assets/images/brutalist/Brutalism-85.png +0 -0
  515. package/sources/assets/images/brutalist/Brutalism-86.png +0 -0
  516. package/sources/assets/images/brutalist/Brutalism-87.png +0 -0
  517. package/sources/assets/images/brutalist/Brutalism-88.png +0 -0
  518. package/sources/assets/images/brutalist/Brutalism-89.png +0 -0
  519. package/sources/assets/images/brutalist/Brutalism-9.png +0 -0
  520. package/sources/assets/images/brutalist/Brutalism-90.png +0 -0
  521. package/sources/assets/images/brutalist/Brutalism-91.png +0 -0
  522. package/sources/assets/images/brutalist/Brutalism-92.png +0 -0
  523. package/sources/assets/images/brutalist/Brutalism-93.png +0 -0
  524. package/sources/assets/images/brutalist/Brutalism-94.png +0 -0
  525. package/sources/assets/images/brutalist/Brutalism-95.png +0 -0
  526. package/sources/assets/images/brutalist/Brutalism-96.png +0 -0
  527. package/sources/assets/images/brutalist/Brutalism-97.png +0 -0
  528. package/sources/assets/images/brutalist/Brutalism-98.png +0 -0
  529. package/sources/assets/images/brutalist/Brutalism-99.png +0 -0
  530. package/sources/assets/images/favicon-active.png +0 -0
  531. package/sources/assets/images/favicon.png +0 -0
  532. package/sources/assets/images/gradients/01.png +0 -0
  533. package/sources/assets/images/gradients/02.png +0 -0
  534. package/sources/assets/images/gradients/03.png +0 -0
  535. package/sources/assets/images/gradients/04.png +0 -0
  536. package/sources/assets/images/gradients/05.png +0 -0
  537. package/sources/assets/images/gradients/06.png +0 -0
  538. package/sources/assets/images/gradients/07.png +0 -0
  539. package/sources/assets/images/gradients/08.png +0 -0
  540. package/sources/assets/images/gradients/09.png +0 -0
  541. package/sources/assets/images/gradients/10.png +0 -0
  542. package/sources/assets/images/gradients/100.png +0 -0
  543. package/sources/assets/images/gradients/11.png +0 -0
  544. package/sources/assets/images/gradients/12.png +0 -0
  545. package/sources/assets/images/gradients/13.png +0 -0
  546. package/sources/assets/images/gradients/14.png +0 -0
  547. package/sources/assets/images/gradients/15.png +0 -0
  548. package/sources/assets/images/gradients/16.png +0 -0
  549. package/sources/assets/images/gradients/17.png +0 -0
  550. package/sources/assets/images/gradients/18.png +0 -0
  551. package/sources/assets/images/gradients/19.png +0 -0
  552. package/sources/assets/images/gradients/20.png +0 -0
  553. package/sources/assets/images/gradients/21.png +0 -0
  554. package/sources/assets/images/gradients/22.png +0 -0
  555. package/sources/assets/images/gradients/23.png +0 -0
  556. package/sources/assets/images/gradients/24.png +0 -0
  557. package/sources/assets/images/gradients/25.png +0 -0
  558. package/sources/assets/images/gradients/26.png +0 -0
  559. package/sources/assets/images/gradients/27.png +0 -0
  560. package/sources/assets/images/gradients/28.png +0 -0
  561. package/sources/assets/images/gradients/29.png +0 -0
  562. package/sources/assets/images/gradients/30.png +0 -0
  563. package/sources/assets/images/gradients/31.png +0 -0
  564. package/sources/assets/images/gradients/32.png +0 -0
  565. package/sources/assets/images/gradients/33.png +0 -0
  566. package/sources/assets/images/gradients/34.png +0 -0
  567. package/sources/assets/images/gradients/35.png +0 -0
  568. package/sources/assets/images/gradients/36.png +0 -0
  569. package/sources/assets/images/gradients/37.png +0 -0
  570. package/sources/assets/images/gradients/38.png +0 -0
  571. package/sources/assets/images/gradients/39.png +0 -0
  572. package/sources/assets/images/gradients/40.png +0 -0
  573. package/sources/assets/images/gradients/41.png +0 -0
  574. package/sources/assets/images/gradients/42.png +0 -0
  575. package/sources/assets/images/gradients/43.png +0 -0
  576. package/sources/assets/images/gradients/44.png +0 -0
  577. package/sources/assets/images/gradients/45.png +0 -0
  578. package/sources/assets/images/gradients/46.png +0 -0
  579. package/sources/assets/images/gradients/47.png +0 -0
  580. package/sources/assets/images/gradients/48.png +0 -0
  581. package/sources/assets/images/gradients/49.png +0 -0
  582. package/sources/assets/images/gradients/50.png +0 -0
  583. package/sources/assets/images/gradients/51.png +0 -0
  584. package/sources/assets/images/gradients/52.png +0 -0
  585. package/sources/assets/images/gradients/53.png +0 -0
  586. package/sources/assets/images/gradients/54.png +0 -0
  587. package/sources/assets/images/gradients/55.png +0 -0
  588. package/sources/assets/images/gradients/56.png +0 -0
  589. package/sources/assets/images/gradients/57.png +0 -0
  590. package/sources/assets/images/gradients/58.png +0 -0
  591. package/sources/assets/images/gradients/59.png +0 -0
  592. package/sources/assets/images/gradients/60.png +0 -0
  593. package/sources/assets/images/gradients/61.png +0 -0
  594. package/sources/assets/images/gradients/62.png +0 -0
  595. package/sources/assets/images/gradients/63.png +0 -0
  596. package/sources/assets/images/gradients/64.png +0 -0
  597. package/sources/assets/images/gradients/65.png +0 -0
  598. package/sources/assets/images/gradients/66.png +0 -0
  599. package/sources/assets/images/gradients/67.png +0 -0
  600. package/sources/assets/images/gradients/68.png +0 -0
  601. package/sources/assets/images/gradients/69.png +0 -0
  602. package/sources/assets/images/gradients/70.png +0 -0
  603. package/sources/assets/images/gradients/71.png +0 -0
  604. package/sources/assets/images/gradients/72.png +0 -0
  605. package/sources/assets/images/gradients/73.png +0 -0
  606. package/sources/assets/images/gradients/74.png +0 -0
  607. package/sources/assets/images/gradients/75.png +0 -0
  608. package/sources/assets/images/gradients/76.png +0 -0
  609. package/sources/assets/images/gradients/77.png +0 -0
  610. package/sources/assets/images/gradients/78.png +0 -0
  611. package/sources/assets/images/gradients/79.png +0 -0
  612. package/sources/assets/images/gradients/80.png +0 -0
  613. package/sources/assets/images/gradients/81.png +0 -0
  614. package/sources/assets/images/gradients/82.png +0 -0
  615. package/sources/assets/images/gradients/83.png +0 -0
  616. package/sources/assets/images/gradients/84.png +0 -0
  617. package/sources/assets/images/gradients/85.png +0 -0
  618. package/sources/assets/images/gradients/86.png +0 -0
  619. package/sources/assets/images/gradients/87.png +0 -0
  620. package/sources/assets/images/gradients/88.png +0 -0
  621. package/sources/assets/images/gradients/89.png +0 -0
  622. package/sources/assets/images/gradients/90.png +0 -0
  623. package/sources/assets/images/gradients/91.png +0 -0
  624. package/sources/assets/images/gradients/92.png +0 -0
  625. package/sources/assets/images/gradients/93.png +0 -0
  626. package/sources/assets/images/gradients/94.png +0 -0
  627. package/sources/assets/images/gradients/95.png +0 -0
  628. package/sources/assets/images/gradients/96.png +0 -0
  629. package/sources/assets/images/gradients/97.png +0 -0
  630. package/sources/assets/images/gradients/98.png +0 -0
  631. package/sources/assets/images/gradients/99.png +0 -0
  632. package/sources/assets/images/icon-adaptive.png +0 -0
  633. package/sources/assets/images/icon-claude.png +0 -0
  634. package/sources/assets/images/icon-claude@2x.png +0 -0
  635. package/sources/assets/images/icon-claude@3x.png +0 -0
  636. package/sources/assets/images/icon-gemini.png +0 -0
  637. package/sources/assets/images/icon-gemini@2x.png +0 -0
  638. package/sources/assets/images/icon-gemini@3x.png +0 -0
  639. package/sources/assets/images/icon-gpt.png +0 -0
  640. package/sources/assets/images/icon-gpt@2x.png +0 -0
  641. package/sources/assets/images/icon-gpt@3x.png +0 -0
  642. package/sources/assets/images/icon-monochrome.png +0 -0
  643. package/sources/assets/images/icon-notification.png +0 -0
  644. package/sources/assets/images/icon-openclaw.png +0 -0
  645. package/sources/assets/images/icon-openclaw@2x.png +0 -0
  646. package/sources/assets/images/icon-openclaw@3x.png +0 -0
  647. package/sources/assets/images/icon-tauri.png +0 -0
  648. package/sources/assets/images/icon-voice-white.png +0 -0
  649. package/sources/assets/images/icon-voice.png +0 -0
  650. package/sources/assets/images/icon-voice@2x.png +0 -0
  651. package/sources/assets/images/icon-voice@3x.png +0 -0
  652. package/sources/assets/images/icon.png +0 -0
  653. package/sources/assets/images/logo-black.png +0 -0
  654. package/sources/assets/images/logo-white.png +0 -0
  655. package/sources/assets/images/logotype-dark.png +0 -0
  656. package/sources/assets/images/logotype-dark@2x.png +0 -0
  657. package/sources/assets/images/logotype-dark@3x.png +0 -0
  658. package/sources/assets/images/logotype-light.png +0 -0
  659. package/sources/assets/images/logotype-light@2x.png +0 -0
  660. package/sources/assets/images/logotype-light@3x.png +0 -0
  661. package/sources/assets/images/logotype.png +0 -0
  662. package/sources/assets/images/logotype@2x.png +0 -0
  663. package/sources/assets/images/logotype@3x.png +0 -0
  664. package/sources/assets/images/splash-android-dark.png +0 -0
  665. package/sources/assets/images/splash-android-light.png +0 -0
  666. package/sources/assets/images/transparent.png +0 -0
  667. package/sources/assets/images/zen-icon.png +0 -0
  668. package/sources/auth/AuthContext.tsx +100 -0
  669. package/sources/auth/authAccountApprove.ts +2 -0
  670. package/sources/auth/authApprove.ts +2 -0
  671. package/sources/auth/authChallenge.ts +8 -0
  672. package/sources/auth/authGetToken.ts +4 -0
  673. package/sources/auth/authQRStart.ts +17 -0
  674. package/sources/auth/authQRWait.ts +13 -0
  675. package/sources/auth/secretKeyBackup.spec.ts +465 -0
  676. package/sources/auth/secretKeyBackup.ts +179 -0
  677. package/sources/auth/tokenStorage.ts +27 -0
  678. package/sources/changelog/changelog.json +60 -0
  679. package/sources/changelog/index.ts +3 -0
  680. package/sources/changelog/parser.ts +23 -0
  681. package/sources/changelog/storage.ts +17 -0
  682. package/sources/changelog/types.ts +10 -0
  683. package/sources/components/ActiveSessionsGroupCompact.tsx +567 -0
  684. package/sources/components/AgentContentView.ios.tsx +70 -0
  685. package/sources/components/AgentContentView.tsx +48 -0
  686. package/sources/components/AgentInput.tsx +1468 -0
  687. package/sources/components/AgentInputAttachmentStrip.tsx +122 -0
  688. package/sources/components/AgentInputAutocomplete.tsx +96 -0
  689. package/sources/components/AgentInputSuggestionView.tsx +106 -0
  690. package/sources/components/AllFilesDiffView.tsx +515 -0
  691. package/sources/components/Avatar.tsx +149 -0
  692. package/sources/components/AvatarBrutalist.tsx +501 -0
  693. package/sources/components/AvatarGradient.tsx +147 -0
  694. package/sources/components/AvatarSkia.tsx +111 -0
  695. package/sources/components/AvatarSkia.web.tsx +113 -0
  696. package/sources/components/ChatFooter.tsx +50 -0
  697. package/sources/components/ChatHeaderView.tsx +180 -0
  698. package/sources/components/ChatList.tsx +283 -0
  699. package/sources/components/CodeEditor.tsx +22 -0
  700. package/sources/components/CodeEditor.web.tsx +180 -0
  701. package/sources/components/CodeView.tsx +33 -0
  702. package/sources/components/CommandPalette/CommandPalette.tsx +72 -0
  703. package/sources/components/CommandPalette/CommandPaletteInput.tsx +65 -0
  704. package/sources/components/CommandPalette/CommandPaletteItem.tsx +141 -0
  705. package/sources/components/CommandPalette/CommandPaletteModal.tsx +148 -0
  706. package/sources/components/CommandPalette/CommandPaletteProvider.tsx +141 -0
  707. package/sources/components/CommandPalette/CommandPaletteResults.tsx +129 -0
  708. package/sources/components/CommandPalette/index.ts +3 -0
  709. package/sources/components/CommandPalette/types.ts +15 -0
  710. package/sources/components/CommandPalette/useCommandPalette.ts +107 -0
  711. package/sources/components/CommandView.tsx +135 -0
  712. package/sources/components/CompactGitStatus.tsx +88 -0
  713. package/sources/components/ConnectButton.tsx +117 -0
  714. package/sources/components/Deferred.tsx +18 -0
  715. package/sources/components/DuplicateSheet.tsx +295 -0
  716. package/sources/components/EmptyMainScreen.tsx +171 -0
  717. package/sources/components/EmptyMessages.tsx +123 -0
  718. package/sources/components/EmptySessionsTablet.tsx +111 -0
  719. package/sources/components/ExternalLink.tsx +22 -0
  720. package/sources/components/FAB.tsx +53 -0
  721. package/sources/components/FABWide.tsx +59 -0
  722. package/sources/components/FeedItemCard.tsx +98 -0
  723. package/sources/components/FileIcon.tsx +63 -0
  724. package/sources/components/FileViewPanel.tsx +673 -0
  725. package/sources/components/FilesSidebar.tsx +739 -0
  726. package/sources/components/FloatingOverlay.tsx +48 -0
  727. package/sources/components/GitStatusBadge.tsx +82 -0
  728. package/sources/components/HeaderLogo.tsx +28 -0
  729. package/sources/components/HomeHeader.tsx +243 -0
  730. package/sources/components/HorizontalScrollView.tsx +88 -0
  731. package/sources/components/InboxView.tsx +260 -0
  732. package/sources/components/InlineFileDiff.tsx +277 -0
  733. package/sources/components/Item.tsx +315 -0
  734. package/sources/components/ItemGroup.tsx +147 -0
  735. package/sources/components/ItemList.tsx +102 -0
  736. package/sources/components/MainView.tsx +324 -0
  737. package/sources/components/MessageView.tsx +299 -0
  738. package/sources/components/MultiTextInput.tsx +285 -0
  739. package/sources/components/MultiTextInput.web.tsx +220 -0
  740. package/sources/components/OAuthView.tsx +374 -0
  741. package/sources/components/PermissionModeSelector.tsx +68 -0
  742. package/sources/components/PlaceholderContainerView.tsx +47 -0
  743. package/sources/components/PlusPlus.tsx +34 -0
  744. package/sources/components/PlusPlus.web.tsx +34 -0
  745. package/sources/components/ProjectGitStatus.tsx +102 -0
  746. package/sources/components/RoundButton.tsx +130 -0
  747. package/sources/components/SearchableListSelector.tsx +675 -0
  748. package/sources/components/SessionActionsNativeMenu.android.tsx +58 -0
  749. package/sources/components/SessionActionsNativeMenu.ios.tsx +55 -0
  750. package/sources/components/SessionActionsNativeMenu.tsx +20 -0
  751. package/sources/components/SessionActionsNativeMenu.web.tsx +13 -0
  752. package/sources/components/SessionActionsPopover.tsx +240 -0
  753. package/sources/components/SessionsList.tsx +471 -0
  754. package/sources/components/SessionsListWrapper.tsx +72 -0
  755. package/sources/components/SettingsView.tsx +470 -0
  756. package/sources/components/SettingsViewWrapper.tsx +21 -0
  757. package/sources/components/Shaker.tsx +42 -0
  758. package/sources/components/Shaker.web.tsx +46 -0
  759. package/sources/components/ShimmerView.tsx +106 -0
  760. package/sources/components/SidebarNavigator.tsx +218 -0
  761. package/sources/components/SidebarView.tsx +104 -0
  762. package/sources/components/SimpleSyntaxHighlighter.tsx +322 -0
  763. package/sources/components/StatusBarProvider.tsx +12 -0
  764. package/sources/components/StatusDot.tsx +49 -0
  765. package/sources/components/StyledText.tsx +35 -0
  766. package/sources/components/Switch.tsx +20 -0
  767. package/sources/components/TabBar.tsx +140 -0
  768. package/sources/components/ToolGroupView.tsx +101 -0
  769. package/sources/components/TransitionStack.tsx +14 -0
  770. package/sources/components/UpdateBanner.tsx +74 -0
  771. package/sources/components/UserCard.tsx +41 -0
  772. package/sources/components/UserSearchResult.tsx +129 -0
  773. package/sources/components/VoiceAssistantStatusBar.tsx +260 -0
  774. package/sources/components/VoiceBars.tsx +95 -0
  775. package/sources/components/autocomplete/applySuggestion.test.ts +194 -0
  776. package/sources/components/autocomplete/applySuggestion.ts +61 -0
  777. package/sources/components/autocomplete/findActiveWord.test.ts +365 -0
  778. package/sources/components/autocomplete/findActiveWord.ts +207 -0
  779. package/sources/components/autocomplete/suggestions.ts +79 -0
  780. package/sources/components/autocomplete/useActiveSuggestions.ts +130 -0
  781. package/sources/components/autocomplete/useActiveWord.ts +19 -0
  782. package/sources/components/diff/DiffView.tsx +188 -0
  783. package/sources/components/diff/PierreDiffView.tsx +253 -0
  784. package/sources/components/diff/calculateDiff.ts +317 -0
  785. package/sources/components/entityColor.ts +51 -0
  786. package/sources/components/haptics.ts +9 -0
  787. package/sources/components/haptics.web.ts +7 -0
  788. package/sources/components/layout.ts +44 -0
  789. package/sources/components/markdown/MarkdownView.tsx +670 -0
  790. package/sources/components/markdown/MermaidRenderer.tsx +233 -0
  791. package/sources/components/markdown/linkUtils.test.ts +17 -0
  792. package/sources/components/markdown/linkUtils.ts +5 -0
  793. package/sources/components/markdown/parseMarkdown.test.ts +64 -0
  794. package/sources/components/markdown/parseMarkdown.ts +46 -0
  795. package/sources/components/markdown/parseMarkdownBlock.test.ts +108 -0
  796. package/sources/components/markdown/parseMarkdownBlock.ts +200 -0
  797. package/sources/components/markdown/parseMarkdownSpans.ts +88 -0
  798. package/sources/components/modelModeOptions.test.ts +114 -0
  799. package/sources/components/modelModeOptions.ts +257 -0
  800. package/sources/components/navigation/Header.tsx +252 -0
  801. package/sources/components/parseLocalCommandMessage.spec.ts +96 -0
  802. package/sources/components/parseLocalCommandMessage.ts +97 -0
  803. package/sources/components/qr/QRCode.tsx +178 -0
  804. package/sources/components/qr/QRCode.web.tsx +229 -0
  805. package/sources/components/qr/index.ts +2 -0
  806. package/sources/components/qr/qrMatrix.ts +48 -0
  807. package/sources/components/tools/PermissionFooter.tsx +527 -0
  808. package/sources/components/tools/ToolDiffView.tsx +60 -0
  809. package/sources/components/tools/ToolError.tsx +49 -0
  810. package/sources/components/tools/ToolFullView.tsx +193 -0
  811. package/sources/components/tools/ToolHeader.tsx +93 -0
  812. package/sources/components/tools/ToolSectionView.tsx +42 -0
  813. package/sources/components/tools/ToolStatusIndicator.tsx +36 -0
  814. package/sources/components/tools/ToolView.tsx +340 -0
  815. package/sources/components/tools/knownTools.tsx +957 -0
  816. package/sources/components/tools/views/AskUserQuestionView.tsx +357 -0
  817. package/sources/components/tools/views/BashView.tsx +47 -0
  818. package/sources/components/tools/views/BashViewFull.tsx +81 -0
  819. package/sources/components/tools/views/CodexBashView.tsx +126 -0
  820. package/sources/components/tools/views/CodexDiffView.tsx +79 -0
  821. package/sources/components/tools/views/CodexPatchView.tsx +186 -0
  822. package/sources/components/tools/views/EditView.tsx +33 -0
  823. package/sources/components/tools/views/EditViewFull.tsx +38 -0
  824. package/sources/components/tools/views/ExitPlanToolView.tsx +21 -0
  825. package/sources/components/tools/views/FileView.tsx +118 -0
  826. package/sources/components/tools/views/GeminiEditView.tsx +75 -0
  827. package/sources/components/tools/views/GeminiExecuteView.tsx +92 -0
  828. package/sources/components/tools/views/MCPToolView.tsx +31 -0
  829. package/sources/components/tools/views/MultiEditView.tsx +41 -0
  830. package/sources/components/tools/views/MultiEditViewFull.tsx +84 -0
  831. package/sources/components/tools/views/TaskView.tsx +129 -0
  832. package/sources/components/tools/views/TodoView.tsx +90 -0
  833. package/sources/components/tools/views/WriteView.tsx +29 -0
  834. package/sources/components/tools/views/_all.tsx +88 -0
  835. package/sources/components/usage/UsageBar.tsx +80 -0
  836. package/sources/components/usage/UsageChart.tsx +164 -0
  837. package/sources/components/usage/UsagePanel.tsx +283 -0
  838. package/sources/components/web/FaviconPermissionIndicator.tsx +44 -0
  839. package/sources/config.ts +3 -0
  840. package/sources/constants/Languages.ts +116 -0
  841. package/sources/constants/Typography.ts +116 -0
  842. package/sources/dev/testRunner.ts +277 -0
  843. package/sources/docs/autocomplete-text-manipulation.md +224 -0
  844. package/sources/encryption/aes.appspec.ts +25 -0
  845. package/sources/encryption/aes.ts +21 -0
  846. package/sources/encryption/aes.web.test.ts +73 -0
  847. package/sources/encryption/aes.web.ts +79 -0
  848. package/sources/encryption/base64.appspec.ts +240 -0
  849. package/sources/encryption/base64.native.ts +12 -0
  850. package/sources/encryption/base64.ts +46 -0
  851. package/sources/encryption/blob.test.ts +120 -0
  852. package/sources/encryption/blob.ts +58 -0
  853. package/sources/encryption/deriveKey.appspec.ts +72 -0
  854. package/sources/encryption/deriveKey.ts +46 -0
  855. package/sources/encryption/hex.ts +17 -0
  856. package/sources/encryption/hmac_sha512.appspec.ts +40 -0
  857. package/sources/encryption/hmac_sha512.ts +42 -0
  858. package/sources/encryption/libsodium.lib.ts +2 -0
  859. package/sources/encryption/libsodium.lib.web.ts +2 -0
  860. package/sources/encryption/libsodium.ts +58 -0
  861. package/sources/encryption/text.test.ts +61 -0
  862. package/sources/encryption/text.ts +11 -0
  863. package/sources/hooks/useAsyncCommand.ts +25 -0
  864. package/sources/hooks/useAttachmentImage.ts +134 -0
  865. package/sources/hooks/useAutocomplete.ts +69 -0
  866. package/sources/hooks/useAutocompleteSession.ts +53 -0
  867. package/sources/hooks/useChangelog.ts +45 -0
  868. package/sources/hooks/useCheckCameraPermissions.ts +25 -0
  869. package/sources/hooks/useConnectAccount.ts +107 -0
  870. package/sources/hooks/useConnectTerminal.ts +112 -0
  871. package/sources/hooks/useDemoMessages.ts +48 -0
  872. package/sources/hooks/useDraft.ts +120 -0
  873. package/sources/hooks/useElapsedTime.ts +36 -0
  874. package/sources/hooks/useGetPath.ts +13 -0
  875. package/sources/hooks/useGitStatusFiles.ts +45 -0
  876. package/sources/hooks/useGlobalKeyboard.ts +29 -0
  877. package/sources/hooks/useGroupedMessages.ts +149 -0
  878. package/sources/hooks/useImagePicker.ts +135 -0
  879. package/sources/hooks/useInboxHasContent.ts +19 -0
  880. package/sources/hooks/useMultiClick.ts +56 -0
  881. package/sources/hooks/useNasTechAction.ts +45 -0
  882. package/sources/hooks/useNativeUpdate.ts +10 -0
  883. package/sources/hooks/useNavigateToSession.ts +20 -0
  884. package/sources/hooks/useNewSessionDraft.ts +70 -0
  885. package/sources/hooks/usePrefetchFileContents.ts +162 -0
  886. package/sources/hooks/useSearch.ts +120 -0
  887. package/sources/hooks/useSessionQuickActions.ts +325 -0
  888. package/sources/hooks/useTauriDrag.ts +76 -0
  889. package/sources/hooks/useTauriZoom.ts +67 -0
  890. package/sources/hooks/useUpdates.ts +84 -0
  891. package/sources/hooks/useVisibleSessionListViewData.ts +65 -0
  892. package/sources/hooks/useWorktreeCleanup.ts +69 -0
  893. package/sources/log.ts +108 -0
  894. package/sources/modal/ModalManager.ts +203 -0
  895. package/sources/modal/ModalProvider.tsx +103 -0
  896. package/sources/modal/components/BaseModal.tsx +122 -0
  897. package/sources/modal/components/CustomModal.tsx +42 -0
  898. package/sources/modal/components/WebAlertModal.tsx +139 -0
  899. package/sources/modal/components/WebPromptModal.tsx +185 -0
  900. package/sources/modal/index.ts +3 -0
  901. package/sources/modal/types.ts +79 -0
  902. package/sources/nastech-wire/index.ts +10 -0
  903. package/sources/nastech-wire/legacyProtocol.ts +27 -0
  904. package/sources/nastech-wire/messageMeta.ts +14 -0
  905. package/sources/nastech-wire/messages.ts +113 -0
  906. package/sources/nastech-wire/sessionProtocol.ts +134 -0
  907. package/sources/nastech-wire/voice.ts +34 -0
  908. package/sources/polyfills/screenOrientation.ts +33 -0
  909. package/sources/realtime/RealtimeProvider.tsx +20 -0
  910. package/sources/realtime/RealtimeProvider.web.tsx +15 -0
  911. package/sources/realtime/RealtimeSession.ts +200 -0
  912. package/sources/realtime/RealtimeVoiceSession.tsx +211 -0
  913. package/sources/realtime/RealtimeVoiceSession.web.tsx +209 -0
  914. package/sources/realtime/hooks/contextFormatters.ts +127 -0
  915. package/sources/realtime/hooks/voiceHooks.ts +232 -0
  916. package/sources/realtime/realtimeClientTools.ts +94 -0
  917. package/sources/realtime/types.ts +19 -0
  918. package/sources/realtime/voiceConfig.ts +31 -0
  919. package/sources/realtime/voiceExperiment.ts +91 -0
  920. package/sources/realtime/voiceSystemPrompt.ts +75 -0
  921. package/sources/scripts/compareTranslations.ts +217 -0
  922. package/sources/scripts/parseChangelog.ts +87 -0
  923. package/sources/sync/__testdata__/trace_0.json +3986 -0
  924. package/sources/sync/__testdata__/trace_1.json +1391 -0
  925. package/sources/sync/__testdata__/trace_2.json +182 -0
  926. package/sources/sync/agentDefaults.ts +108 -0
  927. package/sources/sync/apiArtifacts.ts +143 -0
  928. package/sources/sync/apiAttachments.ts +217 -0
  929. package/sources/sync/apiFeed.ts +60 -0
  930. package/sources/sync/apiFriends.ts +217 -0
  931. package/sources/sync/apiGithub.spec.ts +97 -0
  932. package/sources/sync/apiGithub.ts +103 -0
  933. package/sources/sync/apiKv.ts +270 -0
  934. package/sources/sync/apiPush.ts +83 -0
  935. package/sources/sync/apiServices.ts +64 -0
  936. package/sources/sync/apiSocket.ts +290 -0
  937. package/sources/sync/apiTypes.spec.ts +23 -0
  938. package/sources/sync/apiTypes.ts +213 -0
  939. package/sources/sync/apiUsage.ts +130 -0
  940. package/sources/sync/apiVoice.ts +57 -0
  941. package/sources/sync/appConfig.ts +95 -0
  942. package/sources/sync/artifactTypes.ts +85 -0
  943. package/sources/sync/attachmentTypes.ts +28 -0
  944. package/sources/sync/encryption/artifactEncryption.ts +83 -0
  945. package/sources/sync/encryption/encryption.ts +220 -0
  946. package/sources/sync/encryption/encryptionCache.ts +248 -0
  947. package/sources/sync/encryption/encryptor.appspec.ts +409 -0
  948. package/sources/sync/encryption/encryptor.ts +126 -0
  949. package/sources/sync/encryption/machineEncryption.ts +122 -0
  950. package/sources/sync/encryption/sessionEncryption.ts +207 -0
  951. package/sources/sync/feedTypes.ts +43 -0
  952. package/sources/sync/friendTypes.ts +92 -0
  953. package/sources/sync/git-parsers/LineParser.ts +62 -0
  954. package/sources/sync/git-parsers/parseBranch.ts +97 -0
  955. package/sources/sync/git-parsers/parseDiff.ts +180 -0
  956. package/sources/sync/git-parsers/parseStatus.ts +162 -0
  957. package/sources/sync/git-parsers/parseStatusV2.ts +307 -0
  958. package/sources/sync/gitStatusFiles.ts +185 -0
  959. package/sources/sync/gitStatusSync.ts +282 -0
  960. package/sources/sync/localSettings.ts +67 -0
  961. package/sources/sync/messageMeta.test.ts +87 -0
  962. package/sources/sync/messageMeta.ts +36 -0
  963. package/sources/sync/modeHacks.test.ts +29 -0
  964. package/sources/sync/modeHacks.ts +22 -0
  965. package/sources/sync/nastechApi.ts +124 -0
  966. package/sources/sync/ops.ts +776 -0
  967. package/sources/sync/persistence.ts +322 -0
  968. package/sources/sync/profile.ts +95 -0
  969. package/sources/sync/projectFiles.ts +54 -0
  970. package/sources/sync/prompt/systemPrompt.ts +20 -0
  971. package/sources/sync/purchases.ts +67 -0
  972. package/sources/sync/pushRegistration.ts +229 -0
  973. package/sources/sync/reducer/activityUpdateAccumulator.test.ts +492 -0
  974. package/sources/sync/reducer/activityUpdateAccumulator.ts +96 -0
  975. package/sources/sync/reducer/messageToEvent.ts +85 -0
  976. package/sources/sync/reducer/phase0-skipping.spec.ts +206 -0
  977. package/sources/sync/reducer/reducer.spec.ts +3169 -0
  978. package/sources/sync/reducer/reducer.ts +1214 -0
  979. package/sources/sync/reducer/reducerTracer.spec.ts +502 -0
  980. package/sources/sync/reducer/reducerTracer.ts +310 -0
  981. package/sources/sync/revenueCat/index.ts +21 -0
  982. package/sources/sync/revenueCat/revenueCat.ts +215 -0
  983. package/sources/sync/revenueCat/revenueCat.web.ts +238 -0
  984. package/sources/sync/revenueCat/types.ts +82 -0
  985. package/sources/sync/serverConfig.ts +72 -0
  986. package/sources/sync/settings.spec.ts +456 -0
  987. package/sources/sync/settings.ts +186 -0
  988. package/sources/sync/storage.ts +1653 -0
  989. package/sources/sync/storageTypes.spec.ts +24 -0
  990. package/sources/sync/storageTypes.ts +215 -0
  991. package/sources/sync/suggestionCommands.ts +149 -0
  992. package/sources/sync/suggestionFile.ts +206 -0
  993. package/sources/sync/sync.ts +2555 -0
  994. package/sources/sync/typesMessage.ts +69 -0
  995. package/sources/sync/typesMessageMeta.test.ts +14 -0
  996. package/sources/sync/typesMessageMeta.ts +17 -0
  997. package/sources/sync/typesRaw.spec.ts +2010 -0
  998. package/sources/sync/typesRaw.ts +1183 -0
  999. package/sources/sync/uploadFormFile.ts +29 -0
  1000. package/sources/sync/uploadFormFile.web.ts +14 -0
  1001. package/sources/sync/webTabTitle.ts +58 -0
  1002. package/sources/text/README.md +223 -0
  1003. package/sources/text/_all.ts +104 -0
  1004. package/sources/text/_default.ts +1015 -0
  1005. package/sources/text/index.ts +215 -0
  1006. package/sources/text/translations/ca.ts +993 -0
  1007. package/sources/text/translations/en.ts +1009 -0
  1008. package/sources/text/translations/es.ts +995 -0
  1009. package/sources/text/translations/it.ts +992 -0
  1010. package/sources/text/translations/ja.ts +993 -0
  1011. package/sources/text/translations/pl.ts +1024 -0
  1012. package/sources/text/translations/pt.ts +992 -0
  1013. package/sources/text/translations/ru.ts +1023 -0
  1014. package/sources/text/translations/zh-Hans.ts +992 -0
  1015. package/sources/text/translations/zh-Hant.ts +991 -0
  1016. package/sources/theme.css +72 -0
  1017. package/sources/theme.dark.json +31 -0
  1018. package/sources/theme.figma.json +457 -0
  1019. package/sources/theme.gen.ts +10 -0
  1020. package/sources/theme.light.json +31 -0
  1021. package/sources/theme.ts +452 -0
  1022. package/sources/track/index.ts +171 -0
  1023. package/sources/track/tracking.ts +12 -0
  1024. package/sources/track/useTrackScreens.ts +10 -0
  1025. package/sources/types/react-native-webrtc-web-shim.d.ts +6 -0
  1026. package/sources/unistyles.ts +97 -0
  1027. package/sources/utils/codexUnifiedDiff.spec.ts +43 -0
  1028. package/sources/utils/codexUnifiedDiff.ts +70 -0
  1029. package/sources/utils/consoleLogging.ts +145 -0
  1030. package/sources/utils/copySessionMetadataToClipboard.ts +53 -0
  1031. package/sources/utils/debounce.test.ts +646 -0
  1032. package/sources/utils/debounce.ts +122 -0
  1033. package/sources/utils/deviceCalculations.test.ts +318 -0
  1034. package/sources/utils/deviceCalculations.ts +87 -0
  1035. package/sources/utils/errors.ts +10 -0
  1036. package/sources/utils/formatPermissionParams.ts +23 -0
  1037. package/sources/utils/isTauri.ts +7 -0
  1038. package/sources/utils/loadSkia.ts +3 -0
  1039. package/sources/utils/loadSkia.web.ts +5 -0
  1040. package/sources/utils/lock.ts +40 -0
  1041. package/sources/utils/machineUtils.ts +6 -0
  1042. package/sources/utils/messageUtils.ts +254 -0
  1043. package/sources/utils/microphonePermissions.ts +109 -0
  1044. package/sources/utils/notificationRouting.test.ts +51 -0
  1045. package/sources/utils/notificationRouting.ts +81 -0
  1046. package/sources/utils/oauth.ts +143 -0
  1047. package/sources/utils/openExternalUrl.ts +19 -0
  1048. package/sources/utils/parseToken.ts +23 -0
  1049. package/sources/utils/pasteImages.web.ts +81 -0
  1050. package/sources/utils/pathUtils.spec.ts +226 -0
  1051. package/sources/utils/pathUtils.ts +75 -0
  1052. package/sources/utils/platform.ts +19 -0
  1053. package/sources/utils/readFileBytes.ts +11 -0
  1054. package/sources/utils/readFileBytes.web.ts +12 -0
  1055. package/sources/utils/requestReview.ts +135 -0
  1056. package/sources/utils/responsive.ts +87 -0
  1057. package/sources/utils/resumeCommand.test.ts +78 -0
  1058. package/sources/utils/resumeCommand.ts +70 -0
  1059. package/sources/utils/sessionFileLinks.test.ts +112 -0
  1060. package/sources/utils/sessionFileLinks.ts +388 -0
  1061. package/sources/utils/sessionUtils.ts +226 -0
  1062. package/sources/utils/stringUtils.ts +41 -0
  1063. package/sources/utils/sync.ts +164 -0
  1064. package/sources/utils/thumbhash.ts +17 -0
  1065. package/sources/utils/thumbhash.web.ts +85 -0
  1066. package/sources/utils/time.ts +41 -0
  1067. package/sources/utils/toSnakeCase.test.ts +182 -0
  1068. package/sources/utils/toSnakeCase.ts +40 -0
  1069. package/sources/utils/toolCommand.test.ts +23 -0
  1070. package/sources/utils/toolCommand.ts +35 -0
  1071. package/sources/utils/toolComparison.test.ts +101 -0
  1072. package/sources/utils/toolComparison.ts +73 -0
  1073. package/sources/utils/toolErrorParser.test.ts +126 -0
  1074. package/sources/utils/toolErrorParser.ts +102 -0
  1075. package/sources/utils/trimIdent.ts +27 -0
  1076. package/sources/utils/truncateForLogs.ts +37 -0
  1077. package/sources/utils/versionUtils.test.ts +58 -0
  1078. package/sources/utils/versionUtils.ts +69 -0
  1079. package/sources/utils/web/faviconGenerator.ts +39 -0
  1080. package/sources/utils/worktree.ts +192 -0
  1081. package/sources/wire/index.ts +97 -0
  1082. package/src-tauri/Cargo.lock +5978 -0
  1083. package/src-tauri/Cargo.toml +27 -0
  1084. package/src-tauri/build.rs +3 -0
  1085. package/src-tauri/capabilities/default.json +27 -0
  1086. package/src-tauri/deny.toml +53 -0
  1087. package/src-tauri/entitlements.plist +24 -0
  1088. package/src-tauri/icons/128x128.png +0 -0
  1089. package/src-tauri/icons/128x128@2x.png +0 -0
  1090. package/src-tauri/icons/32x32.png +0 -0
  1091. package/src-tauri/icons/64x64.png +0 -0
  1092. package/src-tauri/icons/Square107x107Logo.png +0 -0
  1093. package/src-tauri/icons/Square142x142Logo.png +0 -0
  1094. package/src-tauri/icons/Square150x150Logo.png +0 -0
  1095. package/src-tauri/icons/Square284x284Logo.png +0 -0
  1096. package/src-tauri/icons/Square30x30Logo.png +0 -0
  1097. package/src-tauri/icons/Square310x310Logo.png +0 -0
  1098. package/src-tauri/icons/Square44x44Logo.png +0 -0
  1099. package/src-tauri/icons/Square71x71Logo.png +0 -0
  1100. package/src-tauri/icons/Square89x89Logo.png +0 -0
  1101. package/src-tauri/icons/StoreLogo.png +0 -0
  1102. package/src-tauri/icons/icon.icns +0 -0
  1103. package/src-tauri/icons/icon.ico +0 -0
  1104. package/src-tauri/icons/icon.png +0 -0
  1105. package/src-tauri/src/lib.rs +18 -0
  1106. package/src-tauri/src/main.rs +6 -0
  1107. package/src-tauri/tauri.conf.json +51 -0
  1108. package/src-tauri/tauri.dev.conf.json +20 -0
  1109. package/src-tauri/tauri.preview.conf.json +20 -0
  1110. package/tsconfig.json +45 -0
  1111. package/vitest.config.ts +26 -0
@@ -0,0 +1,1653 @@
1
+ import { create } from "zustand";
2
+ import { useShallow } from 'zustand/react/shallow'
3
+ import equal from 'fast-deep-equal'
4
+
5
+ function useDeepEqual<T>(selector: (state: StorageState) => T): (state: StorageState) => T {
6
+ const prev = React.useRef<T>(undefined);
7
+ return (state: StorageState) => {
8
+ const next = selector(state);
9
+ return equal(prev.current, next) ? prev.current! : (prev.current = next);
10
+ };
11
+ }
12
+ import { Session, Machine, GitStatus } from "./storageTypes";
13
+ import type { GitStatusFiles } from "./gitStatusFiles";
14
+ import type { ProjectFilesList } from "./projectFiles";
15
+ import { createReducer, reducer, ReducerState } from "./reducer/reducer";
16
+ import { Message } from "./typesMessage";
17
+ import { NormalizedMessage } from "./typesRaw";
18
+ import { isMachineOnline } from '@/utils/machineUtils';
19
+ import { getSessionName, getSessionSubtitle, getSessionAvatarId, type SessionState } from '@/utils/sessionUtils';
20
+ import { applySettings, Settings } from "./settings";
21
+ import { LocalSettings, applyLocalSettings } from "./localSettings";
22
+ import { Purchases, customerInfoToPurchases } from "./purchases";
23
+ import { Profile } from "./profile";
24
+ import { UserProfile, RelationshipUpdatedEvent } from "./friendTypes";
25
+ import { loadSettings, loadLocalSettings, saveLocalSettings, saveSettings, loadPurchases, savePurchases, loadProfile, saveProfile, loadSessionDrafts, saveSessionDrafts, loadSessionPermissionModes, saveSessionPermissionModes, loadSessionModelModes, saveSessionModelModes, loadSessionEffortLevels, saveSessionEffortLevels } from "./persistence";
26
+ import type { CustomerInfo } from './revenueCat/types';
27
+ import React from "react";
28
+ import { sync } from "./sync";
29
+ import { getCurrentRealtimeSessionId, getVoiceSession } from '@/realtime/RealtimeSession';
30
+ import { isMutableTool } from "@/components/tools/knownTools";
31
+ import { DecryptedArtifact } from "./artifactTypes";
32
+ import { FeedItem } from "./feedTypes";
33
+
34
+ // Debounce timer for realtimeMode changes
35
+ let realtimeModeDebounceTimer: ReturnType<typeof setTimeout> | null = null;
36
+ const REALTIME_MODE_DEBOUNCE_MS = 150;
37
+
38
+ /**
39
+ * Centralized session online state resolver
40
+ * Returns either "online" (string) or a timestamp (number) for last seen
41
+ */
42
+ function resolveSessionOnlineState(session: { active: boolean; activeAt: number }): "online" | number {
43
+ // Session is online if the active flag is true
44
+ return session.active ? "online" : session.activeAt;
45
+ }
46
+
47
+ /**
48
+ * Checks if a session should be shown in the active sessions group
49
+ */
50
+ function isSessionActive(session: { active: boolean; activeAt: number }): boolean {
51
+ // Use the active flag directly, no timeout checks
52
+ return session.active;
53
+ }
54
+
55
+ // Known entitlement IDs
56
+ export type KnownEntitlements = 'pro';
57
+
58
+ interface SessionMessages {
59
+ messages: Message[];
60
+ messagesMap: Record<string, Message>;
61
+ reducerState: ReducerState;
62
+ isLoaded: boolean;
63
+ // True when the server reported more older messages exist beyond the
64
+ // oldest one we currently have. Drives the "load older" affordance in
65
+ // the chat list. Defaults to false until the initial fetch resolves —
66
+ // the UI must not show a stale paginate-up spinner before that.
67
+ hasMoreOlder: boolean;
68
+ // True while a backward (older-history) page is in flight. Used by the
69
+ // chat list to render a loading footer at the top of the inverted list
70
+ // and to suppress duplicate triggers from FlatList onEndReached.
71
+ isLoadingOlder: boolean;
72
+ }
73
+
74
+ // Machine type is now imported from storageTypes - represents persisted machine data
75
+
76
+ // Display-only row data — all primitives, cheap to deep-equal
77
+ export interface SessionRowData {
78
+ id: string;
79
+ name: string;
80
+ subtitle: string;
81
+ avatarId: string;
82
+ flavor: string | null;
83
+ state: SessionState;
84
+ // Only present on inactive sessions — active sessions never show "last seen"
85
+ // and activeAt updates on every heartbeat, causing needless deep-equal diffs
86
+ activeAt?: number;
87
+ createdAt?: number;
88
+ hasDraft: boolean;
89
+ active: boolean;
90
+ machineId: string | null;
91
+ path: string | null;
92
+ homeDir: string | null;
93
+ completedTodosCount: number;
94
+ totalTodosCount: number;
95
+ hasUnread: boolean;
96
+ }
97
+
98
+ function buildSessionRowData(session: Session, unreadSessionIds?: Set<string>): SessionRowData {
99
+ const isOnline = session.presence === "online";
100
+ const hasPermissions = !!(session.agentState?.requests && Object.keys(session.agentState.requests).length > 0);
101
+
102
+ let state: SessionState;
103
+ if (!isOnline) {
104
+ state = 'disconnected';
105
+ } else if (hasPermissions) {
106
+ state = 'permission_required';
107
+ } else if (session.thinking) {
108
+ state = 'thinking';
109
+ } else {
110
+ state = 'waiting';
111
+ }
112
+
113
+ return {
114
+ id: session.id,
115
+ name: getSessionName(session),
116
+ subtitle: getSessionSubtitle(session),
117
+ avatarId: getSessionAvatarId(session),
118
+ flavor: session.metadata?.flavor ?? null,
119
+ state,
120
+ ...(!session.active && { activeAt: session.activeAt, createdAt: session.createdAt }),
121
+ hasDraft: !!session.draft,
122
+ active: session.active,
123
+ machineId: session.metadata?.machineId ?? null,
124
+ path: session.metadata?.path ?? null,
125
+ homeDir: session.metadata?.homeDir ?? null,
126
+ completedTodosCount: session.todos?.filter(todo => todo.status === 'completed').length ?? 0,
127
+ totalTodosCount: session.todos?.length ?? 0,
128
+ hasUnread: unreadSessionIds?.has(session.id) ?? false,
129
+ };
130
+ }
131
+
132
+ // Unified list item type for SessionsList component
133
+ export type SessionListViewItem =
134
+ | { type: 'header'; title: string }
135
+ | { type: 'active-sessions'; sessions: SessionRowData[] }
136
+ | { type: 'archive-toggle'; hidden: boolean }
137
+ | { type: 'project-group'; displayPath: string; machine: Machine }
138
+ | { type: 'session'; session: SessionRowData };
139
+
140
+ // Legacy type for backward compatibility - to be removed
141
+ export type SessionListItem = string | Session;
142
+
143
+ interface StorageState {
144
+ settings: Settings;
145
+ settingsVersion: number | null;
146
+ localSettings: LocalSettings;
147
+ purchases: Purchases;
148
+ profile: Profile;
149
+ sessions: Record<string, Session>;
150
+ sessionsData: SessionListItem[] | null; // Legacy - to be removed
151
+ sessionListViewData: SessionListViewItem[] | null;
152
+ sessionMessages: Record<string, SessionMessages>;
153
+ pathGitStatus: Record<string, GitStatus | null>; // keyed by "machineId:path"
154
+ pathGitStatusFiles: Record<string, GitStatusFiles | null>; // keyed by "machineId:path"
155
+ pathProjectFiles: Record<string, ProjectFilesList | null>; // keyed by "machineId:path"
156
+ sessionFileCache: Record<string, Record<string, { content: string | null; diff: string | null; isBinary: boolean; cachedAt: number }>>;
157
+ machines: Record<string, Machine>;
158
+ artifacts: Record<string, DecryptedArtifact>; // New artifacts storage
159
+ friends: Record<string, UserProfile>; // All relationships (friends, pending, requested, etc.)
160
+ users: Record<string, UserProfile | null>; // Global user cache, null = 404/failed fetch
161
+ feedItems: FeedItem[]; // Simple list of feed items
162
+ feedHead: string | null; // Newest cursor
163
+ feedTail: string | null; // Oldest cursor
164
+ feedHasMore: boolean;
165
+ feedLoaded: boolean; // True after initial feed fetch
166
+ friendsLoaded: boolean; // True after initial friends fetch
167
+ realtimeStatus: 'disconnected' | 'connecting' | 'connected' | 'error';
168
+ realtimeMode: 'idle' | 'agent-speaking' | 'user-speaking';
169
+ voiceSessionGeneration: number;
170
+ socketStatus: 'disconnected' | 'connecting' | 'connected' | 'error';
171
+ socketLastConnectedAt: number | null;
172
+ socketLastDisconnectedAt: number | null;
173
+ isDataReady: boolean;
174
+ nativeUpdateStatus: { available: boolean; updateUrl?: string } | null;
175
+ applySessions: (sessions: (Omit<Session, 'presence'> & { presence?: "online" | number })[]) => void;
176
+ applyMachines: (machines: Machine[], replace?: boolean) => void;
177
+ deleteMachine: (machineId: string) => void;
178
+ applyLoaded: () => void;
179
+ applyReady: () => void;
180
+ applyMessages: (sessionId: string, messages: NormalizedMessage[]) => { changed: string[], hasReadyEvent: boolean };
181
+ applyMessagesLoaded: (sessionId: string) => void;
182
+ applyOlderMessagesPagination: (sessionId: string, info: { hasMore: boolean }) => void;
183
+ applyOlderMessagesLoading: (sessionId: string, isLoading: boolean) => void;
184
+ applySettings: (settings: Settings, version: number) => void;
185
+ applySettingsLocal: (settings: Partial<Settings>) => void;
186
+ applyLocalSettings: (settings: Partial<LocalSettings>) => void;
187
+ applyPurchases: (customerInfo: CustomerInfo) => void;
188
+ applyProfile: (profile: Profile) => void;
189
+ applyGitStatus: (pathKey: string, status: GitStatus | null) => void;
190
+ applyGitStatusFiles: (pathKey: string, files: GitStatusFiles | null) => void;
191
+ applyProjectFiles: (pathKey: string, files: ProjectFilesList | null) => void;
192
+ getSessionPathKey: (sessionId: string) => string | null;
193
+ applyFileCache: (sessionId: string, filePath: string, content: string | null, diff: string | null, isBinary: boolean) => void;
194
+ applyNativeUpdateStatus: (status: { available: boolean; updateUrl?: string } | null) => void;
195
+ isMutableToolCall: (sessionId: string, callId: string) => boolean;
196
+ setRealtimeStatus: (status: 'disconnected' | 'connecting' | 'connected' | 'error') => void;
197
+ setRealtimeMode: (mode: 'idle' | 'agent-speaking' | 'user-speaking', immediate?: boolean) => void;
198
+ clearRealtimeModeDebounce: () => void;
199
+ incrementVoiceSessionGeneration: () => void;
200
+ setSocketStatus: (status: 'disconnected' | 'connecting' | 'connected' | 'error') => void;
201
+ getActiveSessions: () => Session[];
202
+ updateSessionDraft: (sessionId: string, draft: string | null) => void;
203
+ updateSessionPermissionMode: (sessionId: string, mode: string | null) => void;
204
+ updateSessionModelMode: (sessionId: string, mode: string | null) => void;
205
+ updateSessionEffortLevel: (sessionId: string, level: string | null) => void;
206
+ resetSessionAgentOverrides: (sessionId: string) => void;
207
+ // Artifact methods
208
+ applyArtifacts: (artifacts: DecryptedArtifact[]) => void;
209
+ addArtifact: (artifact: DecryptedArtifact) => void;
210
+ updateArtifact: (artifact: DecryptedArtifact) => void;
211
+ deleteArtifact: (artifactId: string) => void;
212
+ deleteSession: (sessionId: string) => void;
213
+ // Friend management methods
214
+ applyFriends: (friends: UserProfile[]) => void;
215
+ applyRelationshipUpdate: (event: RelationshipUpdatedEvent) => void;
216
+ getFriend: (userId: string) => UserProfile | undefined;
217
+ getAcceptedFriends: () => UserProfile[];
218
+ // User cache methods
219
+ applyUsers: (users: Record<string, UserProfile | null>) => void;
220
+ getUser: (userId: string) => UserProfile | null | undefined;
221
+ assumeUsers: (userIds: string[]) => Promise<void>;
222
+ // Feed methods
223
+ applyFeedItems: (items: FeedItem[]) => void;
224
+ clearFeed: () => void;
225
+ // Unread session tracking (memory-only)
226
+ unreadSessionIds: Set<string>;
227
+ currentViewingSessionId: string | null;
228
+ markSessionRead: (sessionId: string) => void;
229
+ markSessionUnread: (sessionId: string) => void;
230
+ setCurrentViewingSession: (sessionId: string | null) => void;
231
+ }
232
+
233
+ // Helper function to build unified list view data from sessions and machines
234
+ function buildSessionListViewData(
235
+ sessions: Record<string, Session>,
236
+ unreadSessionIds?: Set<string>,
237
+ ): SessionListViewItem[] {
238
+ // Separate active and inactive sessions
239
+ const activeSessions: Session[] = [];
240
+ const inactiveSessions: Session[] = [];
241
+
242
+ Object.values(sessions).forEach(session => {
243
+ if (isSessionActive(session)) {
244
+ activeSessions.push(session);
245
+ } else {
246
+ inactiveSessions.push(session);
247
+ }
248
+ });
249
+
250
+ // Sort by creation date (newest first) — matches applySessions behavior
251
+ activeSessions.sort((a, b) => b.createdAt - a.createdAt);
252
+ inactiveSessions.sort((a, b) => b.createdAt - a.createdAt);
253
+
254
+ // Build unified list view data
255
+ const listData: SessionListViewItem[] = [];
256
+
257
+ // Add active sessions as a single item at the top (if any)
258
+ if (activeSessions.length > 0) {
259
+ listData.push({ type: 'active-sessions', sessions: activeSessions.map(s => buildSessionRowData(s, unreadSessionIds)) });
260
+ }
261
+
262
+ // Group inactive sessions by date
263
+ const now = new Date();
264
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
265
+ const yesterday = new Date(today.getTime() - 24 * 60 * 60 * 1000);
266
+
267
+ let currentDateGroup: Session[] = [];
268
+ let currentDateString: string | null = null;
269
+
270
+ for (const session of inactiveSessions) {
271
+ const sessionDate = new Date(session.createdAt);
272
+ const dateString = sessionDate.toDateString();
273
+
274
+ if (currentDateString !== dateString) {
275
+ // Process previous group
276
+ if (currentDateGroup.length > 0 && currentDateString) {
277
+ const groupDate = new Date(currentDateString);
278
+ const sessionDateOnly = new Date(groupDate.getFullYear(), groupDate.getMonth(), groupDate.getDate());
279
+
280
+ let headerTitle: string;
281
+ if (sessionDateOnly.getTime() === today.getTime()) {
282
+ headerTitle = 'Today';
283
+ } else if (sessionDateOnly.getTime() === yesterday.getTime()) {
284
+ headerTitle = 'Yesterday';
285
+ } else {
286
+ const diffTime = today.getTime() - sessionDateOnly.getTime();
287
+ const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
288
+ headerTitle = `${diffDays} days ago`;
289
+ }
290
+
291
+ listData.push({ type: 'header', title: headerTitle });
292
+ currentDateGroup.forEach(sess => {
293
+ listData.push({ type: 'session', session: buildSessionRowData(sess, unreadSessionIds) });
294
+ });
295
+ }
296
+
297
+ // Start new group
298
+ currentDateString = dateString;
299
+ currentDateGroup = [session];
300
+ } else {
301
+ currentDateGroup.push(session);
302
+ }
303
+ }
304
+
305
+ // Process final group
306
+ if (currentDateGroup.length > 0 && currentDateString) {
307
+ const groupDate = new Date(currentDateString);
308
+ const sessionDateOnly = new Date(groupDate.getFullYear(), groupDate.getMonth(), groupDate.getDate());
309
+
310
+ let headerTitle: string;
311
+ if (sessionDateOnly.getTime() === today.getTime()) {
312
+ headerTitle = 'Today';
313
+ } else if (sessionDateOnly.getTime() === yesterday.getTime()) {
314
+ headerTitle = 'Yesterday';
315
+ } else {
316
+ const diffTime = today.getTime() - sessionDateOnly.getTime();
317
+ const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
318
+ headerTitle = `${diffDays} days ago`;
319
+ }
320
+
321
+ listData.push({ type: 'header', title: headerTitle });
322
+ currentDateGroup.forEach(sess => {
323
+ listData.push({ type: 'session', session: buildSessionRowData(sess, unreadSessionIds) });
324
+ });
325
+ }
326
+
327
+ return listData;
328
+ }
329
+
330
+ export const storage = create<StorageState>()((set, get) => {
331
+ let { settings, version } = loadSettings();
332
+ let localSettings = loadLocalSettings();
333
+ let purchases = loadPurchases();
334
+ let profile = loadProfile();
335
+ let sessionDrafts = loadSessionDrafts();
336
+ let sessionPermissionModes = loadSessionPermissionModes();
337
+ let sessionModelModes = loadSessionModelModes();
338
+ let sessionEffortLevels = loadSessionEffortLevels();
339
+ return {
340
+ settings,
341
+ settingsVersion: version,
342
+ localSettings,
343
+ purchases,
344
+ profile,
345
+ sessions: {},
346
+ machines: {},
347
+ artifacts: {}, // Initialize artifacts
348
+ friends: {}, // Initialize relationships cache
349
+ users: {}, // Initialize global user cache
350
+ feedItems: [], // Initialize feed items list
351
+ feedHead: null,
352
+ feedTail: null,
353
+ feedHasMore: false,
354
+ feedLoaded: false, // Initialize as false
355
+ friendsLoaded: false, // Initialize as false
356
+ sessionsData: null, // Legacy - to be removed
357
+ sessionListViewData: null,
358
+ sessionMessages: {},
359
+ pathGitStatus: {},
360
+ pathGitStatusFiles: {},
361
+ pathProjectFiles: {},
362
+ sessionFileCache: {},
363
+ realtimeStatus: 'disconnected',
364
+ realtimeMode: 'idle',
365
+ voiceSessionGeneration: 0,
366
+ socketStatus: 'disconnected',
367
+ socketLastConnectedAt: null,
368
+ socketLastDisconnectedAt: null,
369
+ isDataReady: false,
370
+ nativeUpdateStatus: null,
371
+ unreadSessionIds: new Set<string>(),
372
+ currentViewingSessionId: null,
373
+ isMutableToolCall: (sessionId: string, callId: string) => {
374
+ const sessionMessages = get().sessionMessages[sessionId];
375
+ if (!sessionMessages) {
376
+ return true;
377
+ }
378
+ const toolCall = sessionMessages.reducerState.toolIdToMessageId.get(callId);
379
+ if (!toolCall) {
380
+ return true;
381
+ }
382
+ const toolCallMessage = sessionMessages.messagesMap[toolCall];
383
+ if (!toolCallMessage || toolCallMessage.kind !== 'tool-call') {
384
+ return true;
385
+ }
386
+ return toolCallMessage.tool?.name ? isMutableTool(toolCallMessage.tool?.name) : true;
387
+ },
388
+ getActiveSessions: () => {
389
+ const state = get();
390
+ return Object.values(state.sessions).filter(s => s.active);
391
+ },
392
+ applySessions: (sessions: (Omit<Session, 'presence'> & { presence?: "online" | number })[]) => set((state) => {
393
+ // Load drafts and permission modes if sessions are empty (initial load)
394
+ const isInitialLoad = Object.keys(state.sessions).length === 0;
395
+ const savedDrafts = isInitialLoad ? sessionDrafts : {};
396
+ const savedPermissionModes = isInitialLoad ? sessionPermissionModes : {};
397
+ const savedModelModes = isInitialLoad ? sessionModelModes : {};
398
+ const savedEffortLevels = isInitialLoad ? sessionEffortLevels : {};
399
+
400
+ // Merge new sessions with existing ones
401
+ const mergedSessions: Record<string, Session> = { ...state.sessions };
402
+
403
+ // Update sessions with calculated presence using centralized resolver
404
+ sessions.forEach(session => {
405
+ // Use centralized resolver for consistent state management
406
+ const presence = resolveSessionOnlineState(session);
407
+
408
+ // Preserve explicit local overrides if they exist, or load from
409
+ // saved data. Missing/null means "no user override"; the UI and
410
+ // CLI resolve code defaults later.
411
+ const existingDraft = state.sessions[session.id]?.draft;
412
+ const savedDraft = savedDrafts[session.id];
413
+ const savedPermissionMode = savedPermissionModes[session.id] ?? null;
414
+ const existingPermissionModeRaw = state.sessions[session.id]?.permissionMode ?? null;
415
+ const existingPermissionMode = existingPermissionModeRaw === 'default' && savedPermissionMode !== 'default'
416
+ ? null
417
+ : existingPermissionModeRaw;
418
+ const resolvedPermissionMode = existingPermissionMode ?? savedPermissionMode ?? session.permissionMode ?? null;
419
+
420
+ // Restore model mode / effort level from MMKV on first load — server
421
+ // does not sync these, and they used to reset on every app restart (#1028).
422
+ const savedModelMode = savedModelModes[session.id] ?? null;
423
+ const existingModelModeRaw = state.sessions[session.id]?.modelMode ?? null;
424
+ const existingModelMode = existingModelModeRaw === 'default' && savedModelMode !== 'default'
425
+ ? null
426
+ : existingModelModeRaw;
427
+ const resolvedModelMode = existingModelMode ?? savedModelMode ?? session.modelMode ?? null;
428
+ const existingEffortLevel = state.sessions[session.id]?.effortLevel ?? null;
429
+ const resolvedEffortLevel = existingEffortLevel ?? savedEffortLevels[session.id] ?? session.effortLevel ?? null;
430
+
431
+ mergedSessions[session.id] = {
432
+ ...session,
433
+ presence,
434
+ draft: existingDraft || savedDraft || session.draft || null,
435
+ permissionMode: resolvedPermissionMode,
436
+ modelMode: resolvedModelMode,
437
+ effortLevel: resolvedEffortLevel,
438
+ };
439
+ });
440
+
441
+ // Build active set from all sessions (including existing ones)
442
+ const activeSet = new Set<string>();
443
+ Object.values(mergedSessions).forEach(session => {
444
+ if (isSessionActive(session)) {
445
+ activeSet.add(session.id);
446
+ }
447
+ });
448
+
449
+ // Separate active and inactive sessions
450
+ const activeSessions: Session[] = [];
451
+ const inactiveSessions: Session[] = [];
452
+
453
+ // Process all sessions from merged set
454
+ Object.values(mergedSessions).forEach(session => {
455
+ if (activeSet.has(session.id)) {
456
+ activeSessions.push(session);
457
+ } else {
458
+ inactiveSessions.push(session);
459
+ }
460
+ });
461
+
462
+ // Sort both arrays by creation date for stable ordering
463
+ activeSessions.sort((a, b) => b.createdAt - a.createdAt);
464
+ inactiveSessions.sort((a, b) => b.createdAt - a.createdAt);
465
+
466
+ // Build flat list data for FlashList
467
+ const listData: SessionListItem[] = [];
468
+
469
+ if (activeSessions.length > 0) {
470
+ listData.push('online');
471
+ listData.push(...activeSessions);
472
+ }
473
+
474
+ // Legacy sessionsData - to be removed
475
+ // Machines are now integrated into sessionListViewData
476
+
477
+ if (inactiveSessions.length > 0) {
478
+ listData.push('offline');
479
+ listData.push(...inactiveSessions);
480
+ }
481
+
482
+ // console.log(`📊 Storage: applySessions called with ${sessions.length} sessions, active: ${activeSessions.length}, inactive: ${inactiveSessions.length}`);
483
+
484
+ // Process AgentState updates for sessions that already have messages loaded
485
+ const updatedSessionMessages = { ...state.sessionMessages };
486
+
487
+ sessions.forEach(session => {
488
+ const oldSession = state.sessions[session.id];
489
+ const newSession = mergedSessions[session.id];
490
+
491
+ // Check if sessionMessages exists AND agentStateVersion is newer
492
+ const existingSessionMessages = updatedSessionMessages[session.id];
493
+ if (existingSessionMessages && newSession.agentState &&
494
+ (!oldSession || newSession.agentStateVersion > (oldSession.agentStateVersion || 0))) {
495
+
496
+ // Check for NEW permission requests before processing
497
+ const currentRealtimeSessionId = getCurrentRealtimeSessionId();
498
+ const voiceSession = getVoiceSession();
499
+
500
+ // console.log('[REALTIME DEBUG] Permission check:', {
501
+ // currentRealtimeSessionId,
502
+ // sessionId: session.id,
503
+ // match: currentRealtimeSessionId === session.id,
504
+ // hasVoiceSession: !!voiceSession,
505
+ // oldRequests: Object.keys(oldSession?.agentState?.requests || {}),
506
+ // newRequests: Object.keys(newSession.agentState?.requests || {})
507
+ // });
508
+
509
+ if (currentRealtimeSessionId === session.id && voiceSession) {
510
+ const oldRequests = oldSession?.agentState?.requests || {};
511
+ const newRequests = newSession.agentState?.requests || {};
512
+
513
+ // Find NEW permission requests only
514
+ for (const [requestId, request] of Object.entries(newRequests)) {
515
+ if (!oldRequests[requestId]) {
516
+ // This is a NEW permission request
517
+ const toolName = request.tool;
518
+ // console.log('[REALTIME DEBUG] Sending permission notification for:', toolName);
519
+ voiceSession.sendTextMessage(
520
+ `Claude is requesting permission to use the ${toolName} tool`
521
+ );
522
+ }
523
+ }
524
+ }
525
+
526
+ // Process new AgentState through reducer
527
+ const reducerResult = reducer(existingSessionMessages.reducerState, [], newSession.agentState);
528
+ const processedMessages = reducerResult.messages;
529
+
530
+ // Always update the session messages, even if no new messages were created
531
+ // This ensures the reducer state is updated with the new AgentState
532
+ const mergedMessagesMap = { ...existingSessionMessages.messagesMap };
533
+ processedMessages.forEach(message => {
534
+ mergedMessagesMap[message.id] = message;
535
+ });
536
+
537
+ const messagesArray = Object.values(mergedMessagesMap)
538
+ .sort((a, b) => b.createdAt - a.createdAt);
539
+
540
+ updatedSessionMessages[session.id] = {
541
+ messages: messagesArray,
542
+ messagesMap: mergedMessagesMap,
543
+ reducerState: existingSessionMessages.reducerState, // The reducer modifies state in-place, so this has the updates
544
+ isLoaded: existingSessionMessages.isLoaded,
545
+ hasMoreOlder: existingSessionMessages.hasMoreOlder,
546
+ isLoadingOlder: existingSessionMessages.isLoadingOlder
547
+ };
548
+
549
+ // IMPORTANT: Copy latestUsage from reducerState to Session for immediate availability
550
+ if (existingSessionMessages.reducerState.latestUsage) {
551
+ mergedSessions[session.id] = {
552
+ ...mergedSessions[session.id],
553
+ latestUsage: { ...existingSessionMessages.reducerState.latestUsage }
554
+ };
555
+ }
556
+ }
557
+ });
558
+
559
+ // Track unread: detect when agent finishes all work for a request.
560
+ // "Was active" = thinking or had pending permission requests.
561
+ // "Now idle" = online, not thinking, no pending permissions.
562
+ let unreadSessionIds = state.unreadSessionIds;
563
+ sessions.forEach(session => {
564
+ const oldSession = state.sessions[session.id];
565
+ if (!oldSession) return;
566
+ const wasActive = oldSession.thinking === true
567
+ || (oldSession.agentState?.requests && Object.keys(oldSession.agentState.requests).length > 0);
568
+ const newSession = mergedSessions[session.id];
569
+ if (!newSession || !wasActive) return;
570
+ const isNowIdle = newSession.thinking !== true
571
+ && newSession.presence === 'online'
572
+ && (!newSession.agentState?.requests || Object.keys(newSession.agentState.requests).length === 0);
573
+ if (isNowIdle && state.currentViewingSessionId !== session.id) {
574
+ if (!unreadSessionIds.has(session.id)) {
575
+ unreadSessionIds = new Set(unreadSessionIds);
576
+ unreadSessionIds.add(session.id);
577
+ }
578
+ }
579
+ });
580
+
581
+ // Build new unified list view data
582
+ const sessionListViewData = buildSessionListViewData(
583
+ mergedSessions,
584
+ unreadSessionIds,
585
+ );
586
+
587
+ return {
588
+ ...state,
589
+ sessions: mergedSessions,
590
+ sessionsData: listData, // Legacy - to be removed
591
+ sessionListViewData,
592
+ sessionMessages: updatedSessionMessages,
593
+ unreadSessionIds,
594
+ };
595
+ }),
596
+ applyLoaded: () => set((state) => {
597
+ const result = {
598
+ ...state,
599
+ sessionsData: []
600
+ };
601
+ return result;
602
+ }),
603
+ applyReady: () => set((state) => ({
604
+ ...state,
605
+ isDataReady: true
606
+ })),
607
+ applyMessages: (sessionId: string, messages: NormalizedMessage[]) => {
608
+ let changed = new Set<string>();
609
+ let hasReadyEvent = false;
610
+
611
+ // Track plan mode transitions through the batch in order.
612
+ // Set true on EnterPlanMode, false on ExitPlanMode. The final value
613
+ // tells us whether the batch ends with an unresolved plan entry.
614
+ // This prevents history replays (which contain both Enter + Exit) from
615
+ // re-triggering plan mode, while still catching real-time EnterPlanMode.
616
+ let shouldEnterPlanMode = false;
617
+ for (const msg of messages) {
618
+ if (msg.role === 'agent') {
619
+ for (const c of msg.content) {
620
+ if (c.type === 'tool-call') {
621
+ if (c.name === 'EnterPlanMode' || c.name === 'enter_plan_mode') {
622
+ shouldEnterPlanMode = true;
623
+ } else if (c.name === 'ExitPlanMode' || c.name === 'exit_plan_mode') {
624
+ shouldEnterPlanMode = false;
625
+ }
626
+ }
627
+ }
628
+ }
629
+ }
630
+
631
+ set((state) => {
632
+
633
+ // Resolve session messages state
634
+ const existingSession: SessionMessages = state.sessionMessages[sessionId] || {
635
+ messages: [],
636
+ messagesMap: {},
637
+ reducerState: createReducer(),
638
+ isLoaded: false,
639
+ hasMoreOlder: false,
640
+ isLoadingOlder: false
641
+ };
642
+
643
+ // Get the session's agentState if available
644
+ const session = state.sessions[sessionId];
645
+ const agentState = session?.agentState;
646
+
647
+ // Messages are already normalized, no need to process them again
648
+ const normalizedMessages = messages;
649
+
650
+ // Run reducer with agentState
651
+ const reducerResult = reducer(existingSession.reducerState, normalizedMessages, agentState);
652
+ const processedMessages = reducerResult.messages;
653
+ for (let message of processedMessages) {
654
+ changed.add(message.id);
655
+ }
656
+ if (reducerResult.hasReadyEvent) {
657
+ hasReadyEvent = true;
658
+ }
659
+
660
+ // Merge messages
661
+ const mergedMessagesMap = { ...existingSession.messagesMap };
662
+ processedMessages.forEach(message => {
663
+ mergedMessagesMap[message.id] = message;
664
+ });
665
+
666
+ // Convert to array and sort by createdAt
667
+ const messagesArray = Object.values(mergedMessagesMap)
668
+ .sort((a, b) => b.createdAt - a.createdAt);
669
+
670
+ // Update session with todos and latestUsage
671
+ // IMPORTANT: We extract latestUsage from the mutable reducerState and copy it to the Session object
672
+ // This ensures latestUsage is available immediately on load, even before messages are fully loaded
673
+ let updatedSessions = state.sessions;
674
+ const needsUpdate = (reducerResult.todos !== undefined || existingSession.reducerState.latestUsage || shouldEnterPlanMode) && session;
675
+
676
+ if (needsUpdate) {
677
+ updatedSessions = {
678
+ ...state.sessions,
679
+ [sessionId]: {
680
+ ...session,
681
+ ...(reducerResult.todos !== undefined && { todos: reducerResult.todos }),
682
+ // Copy latestUsage from reducerState to make it immediately available
683
+ latestUsage: existingSession.reducerState.latestUsage ? {
684
+ ...existingSession.reducerState.latestUsage
685
+ } : session.latestUsage,
686
+ // Auto-switch to plan mode when EnterPlanMode tool call is detected
687
+ ...(shouldEnterPlanMode && { permissionMode: 'plan' })
688
+ }
689
+ };
690
+ }
691
+
692
+ return {
693
+ ...state,
694
+ sessions: updatedSessions,
695
+ sessionMessages: {
696
+ ...state.sessionMessages,
697
+ [sessionId]: {
698
+ ...existingSession,
699
+ messages: messagesArray,
700
+ messagesMap: mergedMessagesMap,
701
+ reducerState: existingSession.reducerState, // Explicitly include the mutated reducer state
702
+ isLoaded: true
703
+ }
704
+ }
705
+ };
706
+ });
707
+
708
+ // Persist plan mode change
709
+ if (shouldEnterPlanMode) {
710
+ const allModes: Record<string, string> = {};
711
+ const currentState = get();
712
+ Object.entries(currentState.sessions).forEach(([id, sess]) => {
713
+ if (sess.permissionMode && sess.permissionMode !== 'default') {
714
+ allModes[id] = sess.permissionMode;
715
+ }
716
+ });
717
+ saveSessionPermissionModes(allModes);
718
+ }
719
+
720
+ return { changed: Array.from(changed), hasReadyEvent };
721
+ },
722
+ applyMessagesLoaded: (sessionId: string) => set((state) => {
723
+ const existingSession = state.sessionMessages[sessionId];
724
+ let result: StorageState;
725
+
726
+ if (!existingSession) {
727
+ // First time loading - check for AgentState
728
+ const session = state.sessions[sessionId];
729
+ const agentState = session?.agentState;
730
+
731
+ // Create new reducer state
732
+ const reducerState = createReducer();
733
+
734
+ // Process AgentState if it exists
735
+ let messages: Message[] = [];
736
+ let messagesMap: Record<string, Message> = {};
737
+
738
+ if (agentState) {
739
+ // Process AgentState through reducer to get initial permission messages
740
+ const reducerResult = reducer(reducerState, [], agentState);
741
+ const processedMessages = reducerResult.messages;
742
+
743
+ processedMessages.forEach(message => {
744
+ messagesMap[message.id] = message;
745
+ });
746
+
747
+ messages = Object.values(messagesMap)
748
+ .sort((a, b) => b.createdAt - a.createdAt);
749
+ }
750
+
751
+ // Extract latestUsage from reducerState if available and update session
752
+ let updatedSessions = state.sessions;
753
+ if (session && reducerState.latestUsage) {
754
+ updatedSessions = {
755
+ ...state.sessions,
756
+ [sessionId]: {
757
+ ...session,
758
+ latestUsage: { ...reducerState.latestUsage }
759
+ }
760
+ };
761
+ }
762
+
763
+ result = {
764
+ ...state,
765
+ sessions: updatedSessions,
766
+ sessionMessages: {
767
+ ...state.sessionMessages,
768
+ [sessionId]: {
769
+ reducerState,
770
+ messages,
771
+ messagesMap,
772
+ isLoaded: true,
773
+ hasMoreOlder: false,
774
+ isLoadingOlder: false
775
+ } satisfies SessionMessages
776
+ }
777
+ };
778
+ } else {
779
+ result = {
780
+ ...state,
781
+ sessionMessages: {
782
+ ...state.sessionMessages,
783
+ [sessionId]: {
784
+ ...existingSession,
785
+ isLoaded: true
786
+ } satisfies SessionMessages
787
+ }
788
+ };
789
+ }
790
+
791
+ return result;
792
+ }),
793
+ applyOlderMessagesPagination: (sessionId: string, info: { hasMore: boolean }) => set((state) => {
794
+ const existing = state.sessionMessages[sessionId];
795
+ if (!existing) {
796
+ // Pagination metadata is only meaningful once the session has
797
+ // a SessionMessages entry. The fetch path always creates one
798
+ // through applyMessages / applyMessagesLoaded before calling
799
+ // this — but if for any reason it hasn't, ignore the update
800
+ // rather than synthesize a partial entry.
801
+ return state;
802
+ }
803
+ return {
804
+ ...state,
805
+ sessionMessages: {
806
+ ...state.sessionMessages,
807
+ [sessionId]: {
808
+ ...existing,
809
+ hasMoreOlder: info.hasMore
810
+ } satisfies SessionMessages
811
+ }
812
+ };
813
+ }),
814
+ applyOlderMessagesLoading: (sessionId: string, isLoading: boolean) => set((state) => {
815
+ const existing = state.sessionMessages[sessionId];
816
+ if (!existing) {
817
+ return state;
818
+ }
819
+ if (existing.isLoadingOlder === isLoading) {
820
+ return state;
821
+ }
822
+ return {
823
+ ...state,
824
+ sessionMessages: {
825
+ ...state.sessionMessages,
826
+ [sessionId]: {
827
+ ...existing,
828
+ isLoadingOlder: isLoading
829
+ } satisfies SessionMessages
830
+ }
831
+ };
832
+ }),
833
+ applySettingsLocal: (settings: Partial<Settings>) => set((state) => {
834
+ saveSettings(applySettings(state.settings, settings), state.settingsVersion ?? 0);
835
+ return {
836
+ ...state,
837
+ settings: applySettings(state.settings, settings)
838
+ };
839
+ }),
840
+ applySettings: (settings: Settings, version: number) => set((state) => {
841
+ if (state.settingsVersion === null || state.settingsVersion < version) {
842
+ saveSettings(settings, version);
843
+ return {
844
+ ...state,
845
+ settings,
846
+ settingsVersion: version
847
+ };
848
+ } else {
849
+ return state;
850
+ }
851
+ }),
852
+ applyLocalSettings: (delta: Partial<LocalSettings>) => set((state) => {
853
+ const updatedLocalSettings = applyLocalSettings(state.localSettings, delta);
854
+ saveLocalSettings(updatedLocalSettings);
855
+ return {
856
+ ...state,
857
+ localSettings: updatedLocalSettings
858
+ };
859
+ }),
860
+ applyPurchases: (customerInfo: CustomerInfo) => set((state) => {
861
+ // Transform CustomerInfo to our Purchases format
862
+ const purchases = customerInfoToPurchases(customerInfo);
863
+
864
+ // Always save and update - no need for version checks
865
+ savePurchases(purchases);
866
+ return {
867
+ ...state,
868
+ purchases
869
+ };
870
+ }),
871
+ applyProfile: (profile: Profile) => set((state) => {
872
+ // Always save and update profile
873
+ saveProfile(profile);
874
+ return {
875
+ ...state,
876
+ profile
877
+ };
878
+ }),
879
+ applyGitStatus: (pathKey: string, status: GitStatus | null) => set((state) => ({
880
+ ...state,
881
+ pathGitStatus: {
882
+ ...state.pathGitStatus,
883
+ [pathKey]: status
884
+ }
885
+ })),
886
+ applyGitStatusFiles: (pathKey: string, files: GitStatusFiles | null) => set((state) => {
887
+ // Short-circuit on no-op writes. gitStatusSync.invalidate fires on every
888
+ // mutable-tool message and on every update-session, but most of those
889
+ // don't actually change the file set. Without this guard, every fetch
890
+ // produces a fresh object reference, the useSessionGitStatusFiles
891
+ // subscription fires, and AllFilesDiffView nukes its scroll position
892
+ // and re-runs every git diff. fast-deep-equal handles arrays + nested
893
+ // objects so we don't have to enumerate fields.
894
+ if (equal(state.pathGitStatusFiles[pathKey] ?? null, files)) {
895
+ return state;
896
+ }
897
+ return {
898
+ ...state,
899
+ pathGitStatusFiles: {
900
+ ...state.pathGitStatusFiles,
901
+ [pathKey]: files
902
+ }
903
+ };
904
+ }),
905
+ applyProjectFiles: (pathKey: string, files: ProjectFilesList | null) => set((state) => ({
906
+ ...state,
907
+ pathProjectFiles: {
908
+ ...state.pathProjectFiles,
909
+ [pathKey]: files
910
+ }
911
+ })),
912
+ applyFileCache: (sessionId: string, filePath: string, content: string | null, diff: string | null, isBinary: boolean) => set((state) => ({
913
+ ...state,
914
+ sessionFileCache: {
915
+ ...state.sessionFileCache,
916
+ [sessionId]: {
917
+ ...(state.sessionFileCache[sessionId] || {}),
918
+ [filePath]: { content, diff, isBinary, cachedAt: Date.now() }
919
+ }
920
+ }
921
+ })),
922
+ applyNativeUpdateStatus: (status: { available: boolean; updateUrl?: string } | null) => set((state) => ({
923
+ ...state,
924
+ nativeUpdateStatus: status
925
+ })),
926
+ setRealtimeStatus: (status: 'disconnected' | 'connecting' | 'connected' | 'error') => set((state) => ({
927
+ ...state,
928
+ realtimeStatus: status
929
+ })),
930
+ setRealtimeMode: (mode: 'idle' | 'agent-speaking' | 'user-speaking', immediate?: boolean) => {
931
+ if (immediate) {
932
+ // Clear any pending debounce and set immediately
933
+ if (realtimeModeDebounceTimer) {
934
+ clearTimeout(realtimeModeDebounceTimer);
935
+ realtimeModeDebounceTimer = null;
936
+ }
937
+ set((state) => ({ ...state, realtimeMode: mode }));
938
+ } else {
939
+ // Debounce mode changes to avoid flickering
940
+ if (realtimeModeDebounceTimer) {
941
+ clearTimeout(realtimeModeDebounceTimer);
942
+ }
943
+ realtimeModeDebounceTimer = setTimeout(() => {
944
+ realtimeModeDebounceTimer = null;
945
+ set((state) => ({ ...state, realtimeMode: mode }));
946
+ }, REALTIME_MODE_DEBOUNCE_MS);
947
+ }
948
+ },
949
+ clearRealtimeModeDebounce: () => {
950
+ if (realtimeModeDebounceTimer) {
951
+ clearTimeout(realtimeModeDebounceTimer);
952
+ realtimeModeDebounceTimer = null;
953
+ }
954
+ },
955
+ incrementVoiceSessionGeneration: () => set((state) => ({
956
+ ...state,
957
+ voiceSessionGeneration: state.voiceSessionGeneration + 1
958
+ })),
959
+ setSocketStatus: (status: 'disconnected' | 'connecting' | 'connected' | 'error') => set((state) => {
960
+ const now = Date.now();
961
+ const updates: Partial<StorageState> = {
962
+ socketStatus: status
963
+ };
964
+
965
+ // Update timestamp based on status
966
+ if (status === 'connected') {
967
+ updates.socketLastConnectedAt = now;
968
+ } else if (status === 'disconnected' || status === 'error') {
969
+ updates.socketLastDisconnectedAt = now;
970
+ }
971
+
972
+ return {
973
+ ...state,
974
+ ...updates
975
+ };
976
+ }),
977
+ updateSessionDraft: (sessionId: string, draft: string | null) => set((state) => {
978
+ const session = state.sessions[sessionId];
979
+ if (!session) return state;
980
+
981
+ // Don't store empty strings, convert to null
982
+ const normalizedDraft = draft?.trim() ? draft : null;
983
+
984
+ // Collect all drafts for persistence
985
+ const allDrafts: Record<string, string> = {};
986
+ Object.entries(state.sessions).forEach(([id, sess]) => {
987
+ if (id === sessionId) {
988
+ if (normalizedDraft) {
989
+ allDrafts[id] = normalizedDraft;
990
+ }
991
+ } else if (sess.draft) {
992
+ allDrafts[id] = sess.draft;
993
+ }
994
+ });
995
+
996
+ // Persist drafts
997
+ saveSessionDrafts(allDrafts);
998
+
999
+ const updatedSessions = {
1000
+ ...state.sessions,
1001
+ [sessionId]: {
1002
+ ...session,
1003
+ draft: normalizedDraft
1004
+ }
1005
+ };
1006
+
1007
+ return {
1008
+ ...state,
1009
+ sessions: updatedSessions,
1010
+ sessionListViewData: buildSessionListViewData(updatedSessions)
1011
+ };
1012
+ }),
1013
+ updateSessionPermissionMode: (sessionId: string, mode: string | null) => set((state) => {
1014
+ const session = state.sessions[sessionId];
1015
+ if (!session) return state;
1016
+
1017
+ // Update the session with the new permission mode
1018
+ const updatedSessions = {
1019
+ ...state.sessions,
1020
+ [sessionId]: {
1021
+ ...session,
1022
+ permissionMode: mode
1023
+ }
1024
+ };
1025
+
1026
+ // Collect all permission modes for persistence
1027
+ const allModes: Record<string, string> = {};
1028
+ Object.entries(updatedSessions).forEach(([id, sess]) => {
1029
+ if (sess.permissionMode) {
1030
+ allModes[id] = sess.permissionMode;
1031
+ }
1032
+ });
1033
+
1034
+ // Persist only explicit overrides; null/missing means code default.
1035
+ saveSessionPermissionModes(allModes);
1036
+
1037
+ // No need to rebuild sessionListViewData since permission mode doesn't affect the list display
1038
+ return {
1039
+ ...state,
1040
+ sessions: updatedSessions
1041
+ };
1042
+ }),
1043
+ updateSessionModelMode: (sessionId: string, mode: string | null) => set((state) => {
1044
+ const session = state.sessions[sessionId];
1045
+ if (!session) return state;
1046
+
1047
+ // Update the session with the new model mode
1048
+ const updatedSessions = {
1049
+ ...state.sessions,
1050
+ [sessionId]: {
1051
+ ...session,
1052
+ modelMode: mode
1053
+ }
1054
+ };
1055
+
1056
+ // Persist only explicit overrides; null/missing means code default.
1057
+ const allModes: Record<string, string> = {};
1058
+ Object.entries(updatedSessions).forEach(([id, sess]) => {
1059
+ if (sess.modelMode) {
1060
+ allModes[id] = sess.modelMode;
1061
+ }
1062
+ });
1063
+ saveSessionModelModes(allModes);
1064
+
1065
+ // No need to rebuild sessionListViewData since model mode doesn't affect the list display
1066
+ return {
1067
+ ...state,
1068
+ sessions: updatedSessions
1069
+ };
1070
+ }),
1071
+ updateSessionEffortLevel: (sessionId: string, level: string | null) => set((state) => {
1072
+ const session = state.sessions[sessionId];
1073
+ if (!session) return state;
1074
+
1075
+ const updatedSessions = {
1076
+ ...state.sessions,
1077
+ [sessionId]: {
1078
+ ...session,
1079
+ effortLevel: level
1080
+ }
1081
+ };
1082
+
1083
+ // Persist effort levels so the selection survives app restart (#1028).
1084
+ const allLevels: Record<string, string> = {};
1085
+ Object.entries(updatedSessions).forEach(([id, sess]) => {
1086
+ if (sess.effortLevel) {
1087
+ allLevels[id] = sess.effortLevel;
1088
+ }
1089
+ });
1090
+ saveSessionEffortLevels(allLevels);
1091
+
1092
+ return {
1093
+ ...state,
1094
+ sessions: updatedSessions
1095
+ };
1096
+ }),
1097
+ resetSessionAgentOverrides: (sessionId: string) => set((state) => {
1098
+ const session = state.sessions[sessionId];
1099
+ if (!session) return state;
1100
+
1101
+ const updatedSessions = {
1102
+ ...state.sessions,
1103
+ [sessionId]: {
1104
+ ...session,
1105
+ permissionMode: null,
1106
+ modelMode: null,
1107
+ effortLevel: null,
1108
+ }
1109
+ };
1110
+
1111
+ const permissionModes: Record<string, string> = {};
1112
+ const modelModes: Record<string, string> = {};
1113
+ const effortLevels: Record<string, string> = {};
1114
+ Object.entries(updatedSessions).forEach(([id, sess]) => {
1115
+ if (sess.permissionMode) permissionModes[id] = sess.permissionMode;
1116
+ if (sess.modelMode) modelModes[id] = sess.modelMode;
1117
+ if (sess.effortLevel) effortLevels[id] = sess.effortLevel;
1118
+ });
1119
+ saveSessionPermissionModes(permissionModes);
1120
+ saveSessionModelModes(modelModes);
1121
+ saveSessionEffortLevels(effortLevels);
1122
+
1123
+ return {
1124
+ ...state,
1125
+ sessions: updatedSessions
1126
+ };
1127
+ }),
1128
+ getSessionPathKey: (sessionId: string): string | null => {
1129
+ const session = get().sessions[sessionId];
1130
+ if (!session?.metadata?.machineId || !session?.metadata?.path) return null;
1131
+ return `${session.metadata.machineId}:${session.metadata.path}`;
1132
+ },
1133
+ applyMachines: (machines: Machine[], replace: boolean = false) => set((state) => {
1134
+ // Either replace all machines or merge updates
1135
+ let mergedMachines: Record<string, Machine>;
1136
+
1137
+ if (replace) {
1138
+ // Replace entire machine state (used by fetchMachines)
1139
+ mergedMachines = {};
1140
+ machines.forEach(machine => {
1141
+ mergedMachines[machine.id] = machine;
1142
+ });
1143
+ } else {
1144
+ // Merge individual updates (used by update-machine)
1145
+ mergedMachines = { ...state.machines };
1146
+ machines.forEach(machine => {
1147
+ mergedMachines[machine.id] = machine;
1148
+ });
1149
+ }
1150
+
1151
+ // Rebuild sessionListViewData to reflect machine changes
1152
+ const sessionListViewData = buildSessionListViewData(
1153
+ state.sessions
1154
+ );
1155
+
1156
+ return {
1157
+ ...state,
1158
+ machines: mergedMachines,
1159
+ sessionListViewData
1160
+ };
1161
+ }),
1162
+ deleteMachine: (machineId: string) => set((state) => {
1163
+ if (!state.machines[machineId]) {
1164
+ return state;
1165
+ }
1166
+ const { [machineId]: _removed, ...remaining } = state.machines;
1167
+ return {
1168
+ ...state,
1169
+ machines: remaining,
1170
+ sessionListViewData: buildSessionListViewData(state.sessions)
1171
+ };
1172
+ }),
1173
+ // Artifact methods
1174
+ applyArtifacts: (artifacts: DecryptedArtifact[]) => set((state) => {
1175
+ console.log(`🗂️ Storage.applyArtifacts: Applying ${artifacts.length} artifacts`);
1176
+ const mergedArtifacts = { ...state.artifacts };
1177
+ artifacts.forEach(artifact => {
1178
+ mergedArtifacts[artifact.id] = artifact;
1179
+ });
1180
+ console.log(`🗂️ Storage.applyArtifacts: Total artifacts after merge: ${Object.keys(mergedArtifacts).length}`);
1181
+
1182
+ return {
1183
+ ...state,
1184
+ artifacts: mergedArtifacts
1185
+ };
1186
+ }),
1187
+ addArtifact: (artifact: DecryptedArtifact) => set((state) => {
1188
+ const updatedArtifacts = {
1189
+ ...state.artifacts,
1190
+ [artifact.id]: artifact
1191
+ };
1192
+
1193
+ return {
1194
+ ...state,
1195
+ artifacts: updatedArtifacts
1196
+ };
1197
+ }),
1198
+ updateArtifact: (artifact: DecryptedArtifact) => set((state) => {
1199
+ const updatedArtifacts = {
1200
+ ...state.artifacts,
1201
+ [artifact.id]: artifact
1202
+ };
1203
+
1204
+ return {
1205
+ ...state,
1206
+ artifacts: updatedArtifacts
1207
+ };
1208
+ }),
1209
+ deleteArtifact: (artifactId: string) => set((state) => {
1210
+ const { [artifactId]: _, ...remainingArtifacts } = state.artifacts;
1211
+
1212
+ return {
1213
+ ...state,
1214
+ artifacts: remainingArtifacts
1215
+ };
1216
+ }),
1217
+ deleteSession: (sessionId: string) => set((state) => {
1218
+ // Remove session from sessions
1219
+ const { [sessionId]: deletedSession, ...remainingSessions } = state.sessions;
1220
+
1221
+ // Remove session messages if they exist
1222
+ const { [sessionId]: deletedMessages, ...remainingSessionMessages } = state.sessionMessages;
1223
+
1224
+ const { [sessionId]: _fileCache, ...remainingFileCache } = state.sessionFileCache;
1225
+
1226
+ // Clear drafts, permission modes, model modes, effort levels from persistent storage
1227
+ const drafts = loadSessionDrafts();
1228
+ delete drafts[sessionId];
1229
+ saveSessionDrafts(drafts);
1230
+
1231
+ const modes = loadSessionPermissionModes();
1232
+ delete modes[sessionId];
1233
+ saveSessionPermissionModes(modes);
1234
+
1235
+ const modelModes = loadSessionModelModes();
1236
+ delete modelModes[sessionId];
1237
+ saveSessionModelModes(modelModes);
1238
+
1239
+ const effortLevels = loadSessionEffortLevels();
1240
+ delete effortLevels[sessionId];
1241
+ saveSessionEffortLevels(effortLevels);
1242
+
1243
+ // Rebuild sessionListViewData without the deleted session
1244
+ const sessionListViewData = buildSessionListViewData(remainingSessions);
1245
+
1246
+ return {
1247
+ ...state,
1248
+ sessions: remainingSessions,
1249
+ sessionMessages: remainingSessionMessages,
1250
+ sessionFileCache: remainingFileCache,
1251
+ sessionListViewData
1252
+ };
1253
+ }),
1254
+ // Friend management methods
1255
+ applyFriends: (friends: UserProfile[]) => set((state) => {
1256
+ const mergedFriends = { ...state.friends };
1257
+ friends.forEach(friend => {
1258
+ mergedFriends[friend.id] = friend;
1259
+ });
1260
+ return {
1261
+ ...state,
1262
+ friends: mergedFriends,
1263
+ friendsLoaded: true // Mark as loaded after first fetch
1264
+ };
1265
+ }),
1266
+ applyRelationshipUpdate: (event: RelationshipUpdatedEvent) => set((state) => {
1267
+ const { fromUserId, toUserId, status, action, fromUser, toUser } = event;
1268
+ const currentUserId = state.profile.id;
1269
+
1270
+ // Update friends cache
1271
+ const updatedFriends = { ...state.friends };
1272
+
1273
+ // Determine which user profile to update based on perspective
1274
+ const otherUserId = fromUserId === currentUserId ? toUserId : fromUserId;
1275
+ const otherUser = fromUserId === currentUserId ? toUser : fromUser;
1276
+
1277
+ if (action === 'deleted' || status === 'none') {
1278
+ // Remove from friends if deleted or status is none
1279
+ delete updatedFriends[otherUserId];
1280
+ } else if (otherUser) {
1281
+ // Update or add the user profile with current status
1282
+ updatedFriends[otherUserId] = otherUser;
1283
+ }
1284
+
1285
+ return {
1286
+ ...state,
1287
+ friends: updatedFriends
1288
+ };
1289
+ }),
1290
+ getFriend: (userId: string) => {
1291
+ return get().friends[userId];
1292
+ },
1293
+ getAcceptedFriends: () => {
1294
+ const friends = get().friends;
1295
+ return Object.values(friends).filter(friend => friend.status === 'friend');
1296
+ },
1297
+ // User cache methods
1298
+ applyUsers: (users: Record<string, UserProfile | null>) => set((state) => ({
1299
+ ...state,
1300
+ users: { ...state.users, ...users }
1301
+ })),
1302
+ getUser: (userId: string) => {
1303
+ return get().users[userId]; // Returns UserProfile | null | undefined
1304
+ },
1305
+ assumeUsers: async (userIds: string[]) => {
1306
+ // This will be implemented in sync.ts as it needs access to credentials
1307
+ // Just a placeholder here for the interface
1308
+ const { sync } = await import('./sync');
1309
+ return sync.assumeUsers(userIds);
1310
+ },
1311
+ // Feed methods
1312
+ applyFeedItems: (items: FeedItem[]) => set((state) => {
1313
+ // Always mark feed as loaded even if empty
1314
+ if (items.length === 0) {
1315
+ return {
1316
+ ...state,
1317
+ feedLoaded: true // Mark as loaded even when empty
1318
+ };
1319
+ }
1320
+
1321
+ // Create a map of existing items for quick lookup
1322
+ const existingMap = new Map<string, FeedItem>();
1323
+ state.feedItems.forEach(item => {
1324
+ existingMap.set(item.id, item);
1325
+ });
1326
+
1327
+ // Process new items
1328
+ const updatedItems = [...state.feedItems];
1329
+ let head = state.feedHead;
1330
+ let tail = state.feedTail;
1331
+
1332
+ items.forEach(newItem => {
1333
+ // Remove items with same repeatKey if it exists
1334
+ if (newItem.repeatKey) {
1335
+ const indexToRemove = updatedItems.findIndex(item =>
1336
+ item.repeatKey === newItem.repeatKey
1337
+ );
1338
+ if (indexToRemove !== -1) {
1339
+ updatedItems.splice(indexToRemove, 1);
1340
+ }
1341
+ }
1342
+
1343
+ // Add new item if it doesn't exist
1344
+ if (!existingMap.has(newItem.id)) {
1345
+ updatedItems.push(newItem);
1346
+ }
1347
+
1348
+ // Update head/tail cursors
1349
+ if (!head || newItem.counter > parseInt(head.substring(2), 10)) {
1350
+ head = newItem.cursor;
1351
+ }
1352
+ if (!tail || newItem.counter < parseInt(tail.substring(2), 10)) {
1353
+ tail = newItem.cursor;
1354
+ }
1355
+ });
1356
+
1357
+ // Sort by counter (desc - newest first)
1358
+ updatedItems.sort((a, b) => b.counter - a.counter);
1359
+
1360
+ return {
1361
+ ...state,
1362
+ feedItems: updatedItems,
1363
+ feedHead: head,
1364
+ feedTail: tail,
1365
+ feedLoaded: true // Mark as loaded after first fetch
1366
+ };
1367
+ }),
1368
+ clearFeed: () => set((state) => ({
1369
+ ...state,
1370
+ feedItems: [],
1371
+ feedHead: null,
1372
+ feedTail: null,
1373
+ feedHasMore: false,
1374
+ feedLoaded: false, // Reset loading flag
1375
+ friendsLoaded: false // Reset loading flag
1376
+ })),
1377
+ markSessionRead: (sessionId: string) => set((state) => {
1378
+ if (!state.unreadSessionIds.has(sessionId)) return state;
1379
+ const next = new Set(state.unreadSessionIds);
1380
+ next.delete(sessionId);
1381
+ return {
1382
+ ...state,
1383
+ unreadSessionIds: next,
1384
+ sessionListViewData: buildSessionListViewData(state.sessions, next),
1385
+ };
1386
+ }),
1387
+ markSessionUnread: (sessionId: string) => set((state) => {
1388
+ if (state.unreadSessionIds.has(sessionId)) return state;
1389
+ const next = new Set(state.unreadSessionIds);
1390
+ next.add(sessionId);
1391
+ return {
1392
+ ...state,
1393
+ unreadSessionIds: next,
1394
+ sessionListViewData: buildSessionListViewData(state.sessions, next),
1395
+ };
1396
+ }),
1397
+ setCurrentViewingSession: (sessionId: string | null) => set((state) => {
1398
+ if (state.currentViewingSessionId === sessionId) return state;
1399
+ // If switching to a new session, mark it as read
1400
+ const next = sessionId && state.unreadSessionIds.has(sessionId)
1401
+ ? (() => { const s = new Set(state.unreadSessionIds); s.delete(sessionId); return s; })()
1402
+ : state.unreadSessionIds;
1403
+ return {
1404
+ ...state,
1405
+ currentViewingSessionId: sessionId,
1406
+ unreadSessionIds: next,
1407
+ ...(next !== state.unreadSessionIds ? {
1408
+ sessionListViewData: buildSessionListViewData(state.sessions, next),
1409
+ } : {}),
1410
+ };
1411
+ }),
1412
+ }
1413
+ });
1414
+
1415
+ export function useSessions() {
1416
+ return storage(useShallow((state) => state.isDataReady ? state.sessionsData : null));
1417
+ }
1418
+
1419
+ export function useSession(id: string): Session | null {
1420
+ return storage(useShallow((state) => state.sessions[id] ?? null));
1421
+ }
1422
+
1423
+ const emptyArray: unknown[] = [];
1424
+
1425
+ export function useSessionMessages(sessionId: string): {
1426
+ messages: Message[],
1427
+ isLoaded: boolean,
1428
+ hasMoreOlder: boolean,
1429
+ isLoadingOlder: boolean
1430
+ } {
1431
+ return storage(useShallow((state) => {
1432
+ const session = state.sessionMessages[sessionId];
1433
+ return {
1434
+ messages: session?.messages ?? emptyArray,
1435
+ isLoaded: session?.isLoaded ?? false,
1436
+ hasMoreOlder: session?.hasMoreOlder ?? false,
1437
+ isLoadingOlder: session?.isLoadingOlder ?? false
1438
+ };
1439
+ }));
1440
+ }
1441
+
1442
+ export function useMessage(sessionId: string, messageId: string): Message | null {
1443
+ return storage(useShallow((state) => {
1444
+ const session = state.sessionMessages[sessionId];
1445
+ return session?.messagesMap[messageId] ?? null;
1446
+ }));
1447
+ }
1448
+
1449
+ export function useSessionUsage(sessionId: string) {
1450
+ return storage(useShallow((state) => {
1451
+ const session = state.sessionMessages[sessionId];
1452
+ return session?.reducerState?.latestUsage ?? null;
1453
+ }));
1454
+ }
1455
+
1456
+ export function useSettings(): Settings {
1457
+ return storage(useShallow((state) => state.settings));
1458
+ }
1459
+
1460
+ export function useSettingMutable<K extends keyof Settings>(name: K): [Settings[K], (value: Settings[K]) => void] {
1461
+ const setValue = React.useCallback((value: Settings[K]) => {
1462
+ sync.applySettings({ [name]: value });
1463
+ }, [name]);
1464
+ const value = useSetting(name);
1465
+ return [value, setValue];
1466
+ }
1467
+
1468
+ export function useSetting<K extends keyof Settings>(name: K): Settings[K] {
1469
+ return storage(useShallow((state) => state.settings[name]));
1470
+ }
1471
+
1472
+ export function useLocalSettings(): LocalSettings {
1473
+ return storage(useShallow((state) => state.localSettings));
1474
+ }
1475
+
1476
+ export function useAllMachines(options?: { includeOffline?: boolean }): Machine[] {
1477
+ const includeOffline = options?.includeOffline ?? false;
1478
+ return storage(useShallow((state) => {
1479
+ if (!state.isDataReady) return [];
1480
+ const machines = Object.values(state.machines).sort((a, b) => b.createdAt - a.createdAt);
1481
+ return includeOffline ? machines : machines.filter((v) => v.active);
1482
+ }));
1483
+ }
1484
+
1485
+ export function useMachine(machineId: string): Machine | null {
1486
+ return storage(useShallow((state) => state.machines[machineId] ?? null));
1487
+ }
1488
+
1489
+ export function useSessionListViewData(): SessionListViewItem[] | null {
1490
+ return storage(useDeepEqual((state) => state.isDataReady ? state.sessionListViewData : null));
1491
+ }
1492
+
1493
+ export function useAllSessions(): Session[] {
1494
+ return storage(useShallow((state) => {
1495
+ if (!state.isDataReady) return [];
1496
+ return Object.values(state.sessions).sort((a, b) => b.updatedAt - a.updatedAt);
1497
+ }));
1498
+ }
1499
+
1500
+ export function useLocalSettingMutable<K extends keyof LocalSettings>(name: K): [LocalSettings[K], (value: LocalSettings[K]) => void] {
1501
+ const setValue = React.useCallback((value: LocalSettings[K]) => {
1502
+ storage.getState().applyLocalSettings({ [name]: value });
1503
+ }, [name]);
1504
+ const value = useLocalSetting(name);
1505
+ return [value, setValue];
1506
+ }
1507
+
1508
+ export function useLocalSetting<K extends keyof LocalSettings>(name: K): LocalSettings[K] {
1509
+ return storage(useShallow((state) => state.localSettings[name]));
1510
+ }
1511
+
1512
+ export function useIsSessionUnread(sessionId: string): boolean {
1513
+ return storage((state) => state.unreadSessionIds.has(sessionId));
1514
+ }
1515
+
1516
+ // Artifact hooks
1517
+ export function useArtifacts(): DecryptedArtifact[] {
1518
+ return storage(useShallow((state) => {
1519
+ if (!state.isDataReady) return [];
1520
+ // Filter out draft artifacts from the main list
1521
+ return Object.values(state.artifacts)
1522
+ .filter(artifact => !artifact.draft)
1523
+ .sort((a, b) => b.updatedAt - a.updatedAt);
1524
+ }));
1525
+ }
1526
+
1527
+ export function useAllArtifacts(): DecryptedArtifact[] {
1528
+ return storage(useShallow((state) => {
1529
+ if (!state.isDataReady) return [];
1530
+ // Return all artifacts including drafts
1531
+ return Object.values(state.artifacts).sort((a, b) => b.updatedAt - a.updatedAt);
1532
+ }));
1533
+ }
1534
+
1535
+ export function useDraftArtifacts(): DecryptedArtifact[] {
1536
+ return storage(useShallow((state) => {
1537
+ if (!state.isDataReady) return [];
1538
+ // Return only draft artifacts
1539
+ return Object.values(state.artifacts)
1540
+ .filter(artifact => artifact.draft === true)
1541
+ .sort((a, b) => b.updatedAt - a.updatedAt);
1542
+ }));
1543
+ }
1544
+
1545
+ export function useArtifact(artifactId: string): DecryptedArtifact | null {
1546
+ return storage(useShallow((state) => state.artifacts[artifactId] ?? null));
1547
+ }
1548
+
1549
+ export function useArtifactsCount(): number {
1550
+ return storage(useShallow((state) => {
1551
+ // Count only non-draft artifacts
1552
+ return Object.values(state.artifacts).filter(a => !a.draft).length;
1553
+ }));
1554
+ }
1555
+
1556
+ export function useEntitlement(id: KnownEntitlements): boolean {
1557
+ return storage(useShallow((state) => state.purchases.entitlements[id] ?? false));
1558
+ }
1559
+
1560
+ export function useRealtimeStatus(): 'disconnected' | 'connecting' | 'connected' | 'error' {
1561
+ return storage(useShallow((state) => state.realtimeStatus));
1562
+ }
1563
+
1564
+ export function useRealtimeMode(): 'idle' | 'agent-speaking' | 'user-speaking' {
1565
+ return storage(useShallow((state) => state.realtimeMode));
1566
+ }
1567
+
1568
+ export function useVoiceSessionGeneration(): number {
1569
+ return storage(useShallow((state) => state.voiceSessionGeneration));
1570
+ }
1571
+
1572
+ export function useSocketStatus() {
1573
+ return storage(useShallow((state) => ({
1574
+ status: state.socketStatus,
1575
+ lastConnectedAt: state.socketLastConnectedAt,
1576
+ lastDisconnectedAt: state.socketLastDisconnectedAt
1577
+ })));
1578
+ }
1579
+
1580
+ export function useSessionGitStatus(sessionId: string): GitStatus | null {
1581
+ return storage(useShallow((state) => {
1582
+ const pathKey = state.getSessionPathKey(sessionId);
1583
+ return pathKey ? state.pathGitStatus[pathKey] ?? null : null;
1584
+ }));
1585
+ }
1586
+
1587
+ export function useSessionGitStatusFiles(sessionId: string): GitStatusFiles | null {
1588
+ return storage(useShallow((state) => {
1589
+ const pathKey = state.getSessionPathKey(sessionId);
1590
+ return pathKey ? state.pathGitStatusFiles[pathKey] ?? null : null;
1591
+ }));
1592
+ }
1593
+
1594
+ export function useSessionProjectFiles(sessionId: string): ProjectFilesList | null {
1595
+ return storage(useShallow((state) => {
1596
+ const pathKey = state.getSessionPathKey(sessionId);
1597
+ return pathKey ? state.pathProjectFiles[pathKey] ?? null : null;
1598
+ }));
1599
+ }
1600
+
1601
+ export function useSessionFileCache(sessionId: string, filePath: string) {
1602
+ return storage(useShallow((state) => state.sessionFileCache[sessionId]?.[filePath] ?? null));
1603
+ }
1604
+
1605
+ export function useIsDataReady(): boolean {
1606
+ return storage(useShallow((state) => state.isDataReady));
1607
+ }
1608
+
1609
+ export function useProfile() {
1610
+ return storage(useShallow((state) => state.profile));
1611
+ }
1612
+
1613
+ export function useFriends() {
1614
+ return storage(useShallow((state) => state.friends));
1615
+ }
1616
+
1617
+ export function useFriendRequests() {
1618
+ return storage(useShallow((state) => {
1619
+ // Filter friends to get pending requests (where status is 'pending')
1620
+ return Object.values(state.friends).filter(friend => friend.status === 'pending');
1621
+ }));
1622
+ }
1623
+
1624
+ export function useAcceptedFriends() {
1625
+ return storage(useShallow((state) => {
1626
+ return Object.values(state.friends).filter(friend => friend.status === 'friend');
1627
+ }));
1628
+ }
1629
+
1630
+ export function useFeedItems() {
1631
+ return storage(useShallow((state) => state.feedItems));
1632
+ }
1633
+ export function useFeedLoaded() {
1634
+ return storage((state) => state.feedLoaded);
1635
+ }
1636
+ export function useFriendsLoaded() {
1637
+ return storage((state) => state.friendsLoaded);
1638
+ }
1639
+
1640
+ export function useFriend(userId: string | undefined) {
1641
+ return storage(useShallow((state) => userId ? state.friends[userId] : undefined));
1642
+ }
1643
+
1644
+ export function useUser(userId: string | undefined) {
1645
+ return storage(useShallow((state) => userId ? state.users[userId] : undefined));
1646
+ }
1647
+
1648
+ export function useRequestedFriends() {
1649
+ return storage(useShallow((state) => {
1650
+ // Filter friends to get sent requests (where status is 'requested')
1651
+ return Object.values(state.friends).filter(friend => friend.status === 'requested');
1652
+ }));
1653
+ }