availsync 0.1.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 (460) hide show
  1. package/.adal/skills/stripe-best-practices/SKILL.md +42 -0
  2. package/.adal/skills/stripe-best-practices/references/billing.md +36 -0
  3. package/.adal/skills/stripe-best-practices/references/connect.md +48 -0
  4. package/.adal/skills/stripe-best-practices/references/payments.md +79 -0
  5. package/.adal/skills/stripe-best-practices/references/security.md +109 -0
  6. package/.adal/skills/stripe-best-practices/references/treasury.md +16 -0
  7. package/.adal/skills/stripe-projects/SKILL.md +139 -0
  8. package/.adal/skills/upgrade-stripe/SKILL.md +185 -0
  9. package/.agents/skills/stripe-best-practices/SKILL.md +42 -0
  10. package/.agents/skills/stripe-best-practices/references/billing.md +36 -0
  11. package/.agents/skills/stripe-best-practices/references/connect.md +48 -0
  12. package/.agents/skills/stripe-best-practices/references/payments.md +79 -0
  13. package/.agents/skills/stripe-best-practices/references/security.md +109 -0
  14. package/.agents/skills/stripe-best-practices/references/treasury.md +16 -0
  15. package/.agents/skills/stripe-projects/SKILL.md +139 -0
  16. package/.agents/skills/upgrade-stripe/SKILL.md +185 -0
  17. package/.augment/skills/stripe-best-practices/SKILL.md +42 -0
  18. package/.augment/skills/stripe-best-practices/references/billing.md +36 -0
  19. package/.augment/skills/stripe-best-practices/references/connect.md +48 -0
  20. package/.augment/skills/stripe-best-practices/references/payments.md +79 -0
  21. package/.augment/skills/stripe-best-practices/references/security.md +109 -0
  22. package/.augment/skills/stripe-best-practices/references/treasury.md +16 -0
  23. package/.augment/skills/stripe-projects/SKILL.md +139 -0
  24. package/.augment/skills/upgrade-stripe/SKILL.md +185 -0
  25. package/.bob/skills/stripe-best-practices/SKILL.md +42 -0
  26. package/.bob/skills/stripe-best-practices/references/billing.md +36 -0
  27. package/.bob/skills/stripe-best-practices/references/connect.md +48 -0
  28. package/.bob/skills/stripe-best-practices/references/payments.md +79 -0
  29. package/.bob/skills/stripe-best-practices/references/security.md +109 -0
  30. package/.bob/skills/stripe-best-practices/references/treasury.md +16 -0
  31. package/.bob/skills/stripe-projects/SKILL.md +139 -0
  32. package/.bob/skills/upgrade-stripe/SKILL.md +185 -0
  33. package/.claude/settings.local.json +7 -0
  34. package/.claude/skills/stripe-best-practices/SKILL.md +42 -0
  35. package/.claude/skills/stripe-best-practices/references/billing.md +36 -0
  36. package/.claude/skills/stripe-best-practices/references/connect.md +48 -0
  37. package/.claude/skills/stripe-best-practices/references/payments.md +79 -0
  38. package/.claude/skills/stripe-best-practices/references/security.md +109 -0
  39. package/.claude/skills/stripe-best-practices/references/treasury.md +16 -0
  40. package/.claude/skills/stripe-projects/SKILL.md +139 -0
  41. package/.claude/skills/upgrade-stripe/SKILL.md +185 -0
  42. package/.codebuddy/skills/stripe-best-practices/SKILL.md +42 -0
  43. package/.codebuddy/skills/stripe-best-practices/references/billing.md +36 -0
  44. package/.codebuddy/skills/stripe-best-practices/references/connect.md +48 -0
  45. package/.codebuddy/skills/stripe-best-practices/references/payments.md +79 -0
  46. package/.codebuddy/skills/stripe-best-practices/references/security.md +109 -0
  47. package/.codebuddy/skills/stripe-best-practices/references/treasury.md +16 -0
  48. package/.codebuddy/skills/stripe-projects/SKILL.md +139 -0
  49. package/.codebuddy/skills/upgrade-stripe/SKILL.md +185 -0
  50. package/.commandcode/skills/stripe-best-practices/SKILL.md +42 -0
  51. package/.commandcode/skills/stripe-best-practices/references/billing.md +36 -0
  52. package/.commandcode/skills/stripe-best-practices/references/connect.md +48 -0
  53. package/.commandcode/skills/stripe-best-practices/references/payments.md +79 -0
  54. package/.commandcode/skills/stripe-best-practices/references/security.md +109 -0
  55. package/.commandcode/skills/stripe-best-practices/references/treasury.md +16 -0
  56. package/.commandcode/skills/stripe-projects/SKILL.md +139 -0
  57. package/.commandcode/skills/upgrade-stripe/SKILL.md +185 -0
  58. package/.continue/skills/stripe-best-practices/SKILL.md +42 -0
  59. package/.continue/skills/stripe-best-practices/references/billing.md +36 -0
  60. package/.continue/skills/stripe-best-practices/references/connect.md +48 -0
  61. package/.continue/skills/stripe-best-practices/references/payments.md +79 -0
  62. package/.continue/skills/stripe-best-practices/references/security.md +109 -0
  63. package/.continue/skills/stripe-best-practices/references/treasury.md +16 -0
  64. package/.continue/skills/stripe-projects/SKILL.md +139 -0
  65. package/.continue/skills/upgrade-stripe/SKILL.md +185 -0
  66. package/.cortex/skills/stripe-best-practices/SKILL.md +42 -0
  67. package/.cortex/skills/stripe-best-practices/references/billing.md +36 -0
  68. package/.cortex/skills/stripe-best-practices/references/connect.md +48 -0
  69. package/.cortex/skills/stripe-best-practices/references/payments.md +79 -0
  70. package/.cortex/skills/stripe-best-practices/references/security.md +109 -0
  71. package/.cortex/skills/stripe-best-practices/references/treasury.md +16 -0
  72. package/.cortex/skills/stripe-projects/SKILL.md +139 -0
  73. package/.cortex/skills/upgrade-stripe/SKILL.md +185 -0
  74. package/.crush/skills/stripe-best-practices/SKILL.md +42 -0
  75. package/.crush/skills/stripe-best-practices/references/billing.md +36 -0
  76. package/.crush/skills/stripe-best-practices/references/connect.md +48 -0
  77. package/.crush/skills/stripe-best-practices/references/payments.md +79 -0
  78. package/.crush/skills/stripe-best-practices/references/security.md +109 -0
  79. package/.crush/skills/stripe-best-practices/references/treasury.md +16 -0
  80. package/.crush/skills/stripe-projects/SKILL.md +139 -0
  81. package/.crush/skills/upgrade-stripe/SKILL.md +185 -0
  82. package/.env.example +20 -0
  83. package/.factory/skills/stripe-best-practices/SKILL.md +42 -0
  84. package/.factory/skills/stripe-best-practices/references/billing.md +36 -0
  85. package/.factory/skills/stripe-best-practices/references/connect.md +48 -0
  86. package/.factory/skills/stripe-best-practices/references/payments.md +79 -0
  87. package/.factory/skills/stripe-best-practices/references/security.md +109 -0
  88. package/.factory/skills/stripe-best-practices/references/treasury.md +16 -0
  89. package/.factory/skills/stripe-projects/SKILL.md +139 -0
  90. package/.factory/skills/upgrade-stripe/SKILL.md +185 -0
  91. package/.goose/skills/stripe-best-practices/SKILL.md +42 -0
  92. package/.goose/skills/stripe-best-practices/references/billing.md +36 -0
  93. package/.goose/skills/stripe-best-practices/references/connect.md +48 -0
  94. package/.goose/skills/stripe-best-practices/references/payments.md +79 -0
  95. package/.goose/skills/stripe-best-practices/references/security.md +109 -0
  96. package/.goose/skills/stripe-best-practices/references/treasury.md +16 -0
  97. package/.goose/skills/stripe-projects/SKILL.md +139 -0
  98. package/.goose/skills/upgrade-stripe/SKILL.md +185 -0
  99. package/.iflow/skills/stripe-best-practices/SKILL.md +42 -0
  100. package/.iflow/skills/stripe-best-practices/references/billing.md +36 -0
  101. package/.iflow/skills/stripe-best-practices/references/connect.md +48 -0
  102. package/.iflow/skills/stripe-best-practices/references/payments.md +79 -0
  103. package/.iflow/skills/stripe-best-practices/references/security.md +109 -0
  104. package/.iflow/skills/stripe-best-practices/references/treasury.md +16 -0
  105. package/.iflow/skills/stripe-projects/SKILL.md +139 -0
  106. package/.iflow/skills/upgrade-stripe/SKILL.md +185 -0
  107. package/.junie/skills/stripe-best-practices/SKILL.md +42 -0
  108. package/.junie/skills/stripe-best-practices/references/billing.md +36 -0
  109. package/.junie/skills/stripe-best-practices/references/connect.md +48 -0
  110. package/.junie/skills/stripe-best-practices/references/payments.md +79 -0
  111. package/.junie/skills/stripe-best-practices/references/security.md +109 -0
  112. package/.junie/skills/stripe-best-practices/references/treasury.md +16 -0
  113. package/.junie/skills/stripe-projects/SKILL.md +139 -0
  114. package/.junie/skills/upgrade-stripe/SKILL.md +185 -0
  115. package/.kilocode/skills/stripe-best-practices/SKILL.md +42 -0
  116. package/.kilocode/skills/stripe-best-practices/references/billing.md +36 -0
  117. package/.kilocode/skills/stripe-best-practices/references/connect.md +48 -0
  118. package/.kilocode/skills/stripe-best-practices/references/payments.md +79 -0
  119. package/.kilocode/skills/stripe-best-practices/references/security.md +109 -0
  120. package/.kilocode/skills/stripe-best-practices/references/treasury.md +16 -0
  121. package/.kilocode/skills/stripe-projects/SKILL.md +139 -0
  122. package/.kilocode/skills/upgrade-stripe/SKILL.md +185 -0
  123. package/.kiro/skills/stripe-best-practices/SKILL.md +42 -0
  124. package/.kiro/skills/stripe-best-practices/references/billing.md +36 -0
  125. package/.kiro/skills/stripe-best-practices/references/connect.md +48 -0
  126. package/.kiro/skills/stripe-best-practices/references/payments.md +79 -0
  127. package/.kiro/skills/stripe-best-practices/references/security.md +109 -0
  128. package/.kiro/skills/stripe-best-practices/references/treasury.md +16 -0
  129. package/.kiro/skills/stripe-projects/SKILL.md +139 -0
  130. package/.kiro/skills/upgrade-stripe/SKILL.md +185 -0
  131. package/.kode/skills/stripe-best-practices/SKILL.md +42 -0
  132. package/.kode/skills/stripe-best-practices/references/billing.md +36 -0
  133. package/.kode/skills/stripe-best-practices/references/connect.md +48 -0
  134. package/.kode/skills/stripe-best-practices/references/payments.md +79 -0
  135. package/.kode/skills/stripe-best-practices/references/security.md +109 -0
  136. package/.kode/skills/stripe-best-practices/references/treasury.md +16 -0
  137. package/.kode/skills/stripe-projects/SKILL.md +139 -0
  138. package/.kode/skills/upgrade-stripe/SKILL.md +185 -0
  139. package/.mcpjam/skills/stripe-best-practices/SKILL.md +42 -0
  140. package/.mcpjam/skills/stripe-best-practices/references/billing.md +36 -0
  141. package/.mcpjam/skills/stripe-best-practices/references/connect.md +48 -0
  142. package/.mcpjam/skills/stripe-best-practices/references/payments.md +79 -0
  143. package/.mcpjam/skills/stripe-best-practices/references/security.md +109 -0
  144. package/.mcpjam/skills/stripe-best-practices/references/treasury.md +16 -0
  145. package/.mcpjam/skills/stripe-projects/SKILL.md +139 -0
  146. package/.mcpjam/skills/upgrade-stripe/SKILL.md +185 -0
  147. package/.mux/skills/stripe-best-practices/SKILL.md +42 -0
  148. package/.mux/skills/stripe-best-practices/references/billing.md +36 -0
  149. package/.mux/skills/stripe-best-practices/references/connect.md +48 -0
  150. package/.mux/skills/stripe-best-practices/references/payments.md +79 -0
  151. package/.mux/skills/stripe-best-practices/references/security.md +109 -0
  152. package/.mux/skills/stripe-best-practices/references/treasury.md +16 -0
  153. package/.mux/skills/stripe-projects/SKILL.md +139 -0
  154. package/.mux/skills/upgrade-stripe/SKILL.md +185 -0
  155. package/.neovate/skills/stripe-best-practices/SKILL.md +42 -0
  156. package/.neovate/skills/stripe-best-practices/references/billing.md +36 -0
  157. package/.neovate/skills/stripe-best-practices/references/connect.md +48 -0
  158. package/.neovate/skills/stripe-best-practices/references/payments.md +79 -0
  159. package/.neovate/skills/stripe-best-practices/references/security.md +109 -0
  160. package/.neovate/skills/stripe-best-practices/references/treasury.md +16 -0
  161. package/.neovate/skills/stripe-projects/SKILL.md +139 -0
  162. package/.neovate/skills/upgrade-stripe/SKILL.md +185 -0
  163. package/.nixpacksignore +14 -0
  164. package/.openhands/skills/stripe-best-practices/SKILL.md +42 -0
  165. package/.openhands/skills/stripe-best-practices/references/billing.md +36 -0
  166. package/.openhands/skills/stripe-best-practices/references/connect.md +48 -0
  167. package/.openhands/skills/stripe-best-practices/references/payments.md +79 -0
  168. package/.openhands/skills/stripe-best-practices/references/security.md +109 -0
  169. package/.openhands/skills/stripe-best-practices/references/treasury.md +16 -0
  170. package/.openhands/skills/stripe-projects/SKILL.md +139 -0
  171. package/.openhands/skills/upgrade-stripe/SKILL.md +185 -0
  172. package/.pi/skills/stripe-best-practices/SKILL.md +42 -0
  173. package/.pi/skills/stripe-best-practices/references/billing.md +36 -0
  174. package/.pi/skills/stripe-best-practices/references/connect.md +48 -0
  175. package/.pi/skills/stripe-best-practices/references/payments.md +79 -0
  176. package/.pi/skills/stripe-best-practices/references/security.md +109 -0
  177. package/.pi/skills/stripe-best-practices/references/treasury.md +16 -0
  178. package/.pi/skills/stripe-projects/SKILL.md +139 -0
  179. package/.pi/skills/upgrade-stripe/SKILL.md +185 -0
  180. package/.pochi/skills/stripe-best-practices/SKILL.md +42 -0
  181. package/.pochi/skills/stripe-best-practices/references/billing.md +36 -0
  182. package/.pochi/skills/stripe-best-practices/references/connect.md +48 -0
  183. package/.pochi/skills/stripe-best-practices/references/payments.md +79 -0
  184. package/.pochi/skills/stripe-best-practices/references/security.md +109 -0
  185. package/.pochi/skills/stripe-best-practices/references/treasury.md +16 -0
  186. package/.pochi/skills/stripe-projects/SKILL.md +139 -0
  187. package/.pochi/skills/upgrade-stripe/SKILL.md +185 -0
  188. package/.qoder/skills/stripe-best-practices/SKILL.md +42 -0
  189. package/.qoder/skills/stripe-best-practices/references/billing.md +36 -0
  190. package/.qoder/skills/stripe-best-practices/references/connect.md +48 -0
  191. package/.qoder/skills/stripe-best-practices/references/payments.md +79 -0
  192. package/.qoder/skills/stripe-best-practices/references/security.md +109 -0
  193. package/.qoder/skills/stripe-best-practices/references/treasury.md +16 -0
  194. package/.qoder/skills/stripe-projects/SKILL.md +139 -0
  195. package/.qoder/skills/upgrade-stripe/SKILL.md +185 -0
  196. package/.qwen/skills/stripe-best-practices/SKILL.md +42 -0
  197. package/.qwen/skills/stripe-best-practices/references/billing.md +36 -0
  198. package/.qwen/skills/stripe-best-practices/references/connect.md +48 -0
  199. package/.qwen/skills/stripe-best-practices/references/payments.md +79 -0
  200. package/.qwen/skills/stripe-best-practices/references/security.md +109 -0
  201. package/.qwen/skills/stripe-best-practices/references/treasury.md +16 -0
  202. package/.qwen/skills/stripe-projects/SKILL.md +139 -0
  203. package/.qwen/skills/upgrade-stripe/SKILL.md +185 -0
  204. package/.roo/skills/stripe-best-practices/SKILL.md +42 -0
  205. package/.roo/skills/stripe-best-practices/references/billing.md +36 -0
  206. package/.roo/skills/stripe-best-practices/references/connect.md +48 -0
  207. package/.roo/skills/stripe-best-practices/references/payments.md +79 -0
  208. package/.roo/skills/stripe-best-practices/references/security.md +109 -0
  209. package/.roo/skills/stripe-best-practices/references/treasury.md +16 -0
  210. package/.roo/skills/stripe-projects/SKILL.md +139 -0
  211. package/.roo/skills/upgrade-stripe/SKILL.md +185 -0
  212. package/.trae/skills/stripe-best-practices/SKILL.md +42 -0
  213. package/.trae/skills/stripe-best-practices/references/billing.md +36 -0
  214. package/.trae/skills/stripe-best-practices/references/connect.md +48 -0
  215. package/.trae/skills/stripe-best-practices/references/payments.md +79 -0
  216. package/.trae/skills/stripe-best-practices/references/security.md +109 -0
  217. package/.trae/skills/stripe-best-practices/references/treasury.md +16 -0
  218. package/.trae/skills/stripe-projects/SKILL.md +139 -0
  219. package/.trae/skills/upgrade-stripe/SKILL.md +185 -0
  220. package/.vibe/skills/stripe-best-practices/SKILL.md +42 -0
  221. package/.vibe/skills/stripe-best-practices/references/billing.md +36 -0
  222. package/.vibe/skills/stripe-best-practices/references/connect.md +48 -0
  223. package/.vibe/skills/stripe-best-practices/references/payments.md +79 -0
  224. package/.vibe/skills/stripe-best-practices/references/security.md +109 -0
  225. package/.vibe/skills/stripe-best-practices/references/treasury.md +16 -0
  226. package/.vibe/skills/stripe-projects/SKILL.md +139 -0
  227. package/.vibe/skills/upgrade-stripe/SKILL.md +185 -0
  228. package/.windsurf/skills/stripe-best-practices/SKILL.md +42 -0
  229. package/.windsurf/skills/stripe-best-practices/references/billing.md +36 -0
  230. package/.windsurf/skills/stripe-best-practices/references/connect.md +48 -0
  231. package/.windsurf/skills/stripe-best-practices/references/payments.md +79 -0
  232. package/.windsurf/skills/stripe-best-practices/references/security.md +109 -0
  233. package/.windsurf/skills/stripe-best-practices/references/treasury.md +16 -0
  234. package/.windsurf/skills/stripe-projects/SKILL.md +139 -0
  235. package/.windsurf/skills/upgrade-stripe/SKILL.md +185 -0
  236. package/.zencoder/skills/stripe-best-practices/SKILL.md +42 -0
  237. package/.zencoder/skills/stripe-best-practices/references/billing.md +36 -0
  238. package/.zencoder/skills/stripe-best-practices/references/connect.md +48 -0
  239. package/.zencoder/skills/stripe-best-practices/references/payments.md +79 -0
  240. package/.zencoder/skills/stripe-best-practices/references/security.md +109 -0
  241. package/.zencoder/skills/stripe-best-practices/references/treasury.md +16 -0
  242. package/.zencoder/skills/stripe-projects/SKILL.md +139 -0
  243. package/.zencoder/skills/upgrade-stripe/SKILL.md +185 -0
  244. package/AUDIT.md +95 -0
  245. package/BLOCKERS.md +0 -0
  246. package/COOLIFY.md +51 -0
  247. package/MCP_SETUP.md +23 -0
  248. package/PRODUCTION_CHECKLIST.md +246 -0
  249. package/README.md +47 -0
  250. package/ROADMAP.md +91 -0
  251. package/docs/superpowers/plans/2026-05-11-availsync-frontend-sales-flow.md +2445 -0
  252. package/frontend/.env.example +2 -0
  253. package/frontend/app/admin/layout.tsx +13 -0
  254. package/frontend/app/admin/page.tsx +747 -0
  255. package/frontend/app/app/activity/page.tsx +257 -0
  256. package/frontend/app/app/agents/[agentId]/page.tsx +21 -0
  257. package/frontend/app/app/agents/page.tsx +1155 -0
  258. package/frontend/app/app/audit/page.tsx +225 -0
  259. package/frontend/app/app/availability/page.tsx +840 -0
  260. package/frontend/app/app/holds/page.tsx +262 -0
  261. package/frontend/app/app/layout.tsx +19 -0
  262. package/frontend/app/app/onboarding/page.tsx +10 -0
  263. package/frontend/app/app/onboarding/verify/page.tsx +309 -0
  264. package/frontend/app/app/page.tsx +508 -0
  265. package/frontend/app/app/settings/page.tsx +399 -0
  266. package/frontend/app/app/work/page.tsx +426 -0
  267. package/frontend/app/changelog/page.tsx +93 -0
  268. package/frontend/app/checkout/page.tsx +25 -0
  269. package/frontend/app/docs/api/page.tsx +157 -0
  270. package/frontend/app/docs/page.tsx +296 -0
  271. package/frontend/app/docs/pilot/page.tsx +127 -0
  272. package/frontend/app/docs/quickstart/page.tsx +318 -0
  273. package/frontend/app/docs/reliability/page.tsx +78 -0
  274. package/frontend/app/docs/sdk/node/page.tsx +166 -0
  275. package/frontend/app/globals.css +57 -0
  276. package/frontend/app/icon.png +0 -0
  277. package/frontend/app/layout.tsx +87 -0
  278. package/frontend/app/login/page.tsx +14 -0
  279. package/frontend/app/page.tsx +47 -0
  280. package/frontend/app/pricing/page.tsx +66 -0
  281. package/frontend/app/privacy/page.tsx +52 -0
  282. package/frontend/app/robots.ts +26 -0
  283. package/frontend/app/security/page.tsx +74 -0
  284. package/frontend/app/signup/page.tsx +14 -0
  285. package/frontend/app/sitemap.ts +14 -0
  286. package/frontend/app/terms/page.tsx +51 -0
  287. package/frontend/components/brand/AvailsyncLogo.tsx +56 -0
  288. package/frontend/components/checkout/CheckoutClient.tsx +100 -0
  289. package/frontend/components/dashboard/AgentForm.tsx +59 -0
  290. package/frontend/components/dashboard/AppShell.tsx +291 -0
  291. package/frontend/components/dashboard/AvailabilityChecker.tsx +117 -0
  292. package/frontend/components/dashboard/AvailabilityWindowForm.tsx +40 -0
  293. package/frontend/components/dashboard/HoldForm.tsx +133 -0
  294. package/frontend/components/dashboard/MetricCard.tsx +10 -0
  295. package/frontend/components/login/LoginForm.tsx +95 -0
  296. package/frontend/components/marketing/AgentCoordinationStory.tsx +1530 -0
  297. package/frontend/components/marketing/Faq.tsx +41 -0
  298. package/frontend/components/marketing/Hero.tsx +73 -0
  299. package/frontend/components/marketing/HowItWorks.tsx +28 -0
  300. package/frontend/components/marketing/ObserveModeTeaser.tsx +41 -0
  301. package/frontend/components/marketing/PricingTeaser.tsx +23 -0
  302. package/frontend/components/marketing/ProblemSolution.tsx +36 -0
  303. package/frontend/components/marketing/SiteFooter.tsx +59 -0
  304. package/frontend/components/marketing/SiteHeader.tsx +45 -0
  305. package/frontend/components/marketing/UseCases.tsx +27 -0
  306. package/frontend/components/onboarding/OnboardingClient.tsx +278 -0
  307. package/frontend/components/pricing/PricingCards.tsx +65 -0
  308. package/frontend/components/privacy/CookieConsent.tsx +230 -0
  309. package/frontend/components/privacy/CookieSettingsButton.tsx +15 -0
  310. package/frontend/components/seo/JsonLd.tsx +10 -0
  311. package/frontend/components/signup/SignupForm.tsx +55 -0
  312. package/frontend/components/ui/Badge.tsx +23 -0
  313. package/frontend/components/ui/Button.tsx +37 -0
  314. package/frontend/components/ui/Card.tsx +11 -0
  315. package/frontend/components/ui/ConfirmDialog.tsx +77 -0
  316. package/frontend/components/ui/EmptyState.tsx +24 -0
  317. package/frontend/components/ui/Input.tsx +14 -0
  318. package/frontend/components/ui/KeyDisplay.tsx +49 -0
  319. package/frontend/components/ui/Select.tsx +14 -0
  320. package/frontend/components/ui/Skeleton.tsx +24 -0
  321. package/frontend/components/ui/Tabs.tsx +19 -0
  322. package/frontend/components/ui/Textarea.tsx +14 -0
  323. package/frontend/components/ui/Toast.tsx +78 -0
  324. package/frontend/components/waitlist/WaitlistDialog.tsx +128 -0
  325. package/frontend/lib/api.ts +1282 -0
  326. package/frontend/lib/billing.ts +6 -0
  327. package/frontend/lib/cookieConsent.ts +113 -0
  328. package/frontend/lib/format.ts +16 -0
  329. package/frontend/lib/plans.ts +62 -0
  330. package/frontend/lib/schemas.ts +108 -0
  331. package/frontend/lib/seo.ts +376 -0
  332. package/frontend/lib/setupGuides.ts +630 -0
  333. package/frontend/lib/storage.ts +30 -0
  334. package/frontend/next-env.d.ts +6 -0
  335. package/frontend/next.config.mjs +13 -0
  336. package/frontend/package-lock.json +14409 -0
  337. package/frontend/package.json +41 -0
  338. package/frontend/playwright.config.ts +20 -0
  339. package/frontend/postcss.config.mjs +8 -0
  340. package/frontend/public/.gitkeep +0 -0
  341. package/frontend/public/brand/availsync-logo-board.png +0 -0
  342. package/frontend/public/brand/availsync-logo-dark.png +0 -0
  343. package/frontend/public/brand/availsync-mark-dark.png +0 -0
  344. package/frontend/public/brand/availsync-wordmark-dark.png +0 -0
  345. package/frontend/public/marketing/hero-agent-coordination.png +0 -0
  346. package/frontend/tailwind.config.ts +53 -0
  347. package/frontend/tests/smoke.spec.ts +89 -0
  348. package/frontend/tsconfig.json +23 -0
  349. package/jest.config.js +7 -0
  350. package/nixpacks.toml +11 -0
  351. package/package.json +53 -0
  352. package/packages/mcp/LICENSE +21 -0
  353. package/packages/mcp/README.md +60 -0
  354. package/packages/mcp/jest.config.cjs +8 -0
  355. package/packages/mcp/package.json +54 -0
  356. package/packages/mcp/src/helpers.ts +38 -0
  357. package/packages/mcp/src/index.test.ts +60 -0
  358. package/packages/mcp/src/index.ts +387 -0
  359. package/packages/mcp/tsconfig.json +20 -0
  360. package/packages/mcp/tsconfig.test.json +12 -0
  361. package/packages/node/LICENSE +21 -0
  362. package/packages/node/README.md +120 -0
  363. package/packages/node/jest.config.cjs +8 -0
  364. package/packages/node/package.json +46 -0
  365. package/packages/node/src/index.test.ts +360 -0
  366. package/packages/node/src/index.ts +402 -0
  367. package/packages/node/tsconfig.json +20 -0
  368. package/packages/node/tsconfig.test.json +12 -0
  369. package/plan.md +923 -0
  370. package/skills/stripe-best-practices/SKILL.md +42 -0
  371. package/skills/stripe-best-practices/references/billing.md +36 -0
  372. package/skills/stripe-best-practices/references/connect.md +48 -0
  373. package/skills/stripe-best-practices/references/payments.md +79 -0
  374. package/skills/stripe-best-practices/references/security.md +109 -0
  375. package/skills/stripe-best-practices/references/treasury.md +16 -0
  376. package/skills/stripe-projects/SKILL.md +139 -0
  377. package/skills/upgrade-stripe/SKILL.md +185 -0
  378. package/skills-lock.json +20 -0
  379. package/src/core/availability.ts +178 -0
  380. package/src/core/conflict.ts +209 -0
  381. package/src/core/work.ts +490 -0
  382. package/src/db/client.ts +17 -0
  383. package/src/db/migrations/001_init.sql +88 -0
  384. package/src/db/migrations/002_stripe.sql +2 -0
  385. package/src/db/migrations/003_workspace_auth.sql +19 -0
  386. package/src/db/migrations/004_agent_mcp_status.sql +2 -0
  387. package/src/db/migrations/005_hold_event_actor.sql +4 -0
  388. package/src/db/migrations/006_agent_activity.sql +35 -0
  389. package/src/db/migrations/007_work_coordination.sql +60 -0
  390. package/src/db/migrations/008_work_claim_leases.sql +20 -0
  391. package/src/db/migrations/009_billing_subscription_state.sql +23 -0
  392. package/src/db/migrations/010_agent_api_key_prefix.sql +10 -0
  393. package/src/db/migrations/011_org_verified_and_work_event_retention.sql +11 -0
  394. package/src/db/migrations/012_agent_enforcement_mode.sql +12 -0
  395. package/src/db/migrations/013_support_tickets.sql +21 -0
  396. package/src/db/migrations/014_paid_plan_waitlist.sql +23 -0
  397. package/src/db/migrations/015_agent_last_seen.sql +2 -0
  398. package/src/db/migrations.ts +164 -0
  399. package/src/db/run-migrations.ts +13 -0
  400. package/src/index.ts +183 -0
  401. package/src/lib/activity.ts +137 -0
  402. package/src/lib/apiKeys.ts +32 -0
  403. package/src/lib/appInfo.ts +26 -0
  404. package/src/lib/billingConfig.ts +3 -0
  405. package/src/lib/env.ts +75 -0
  406. package/src/lib/logger.ts +8 -0
  407. package/src/lib/plans.ts +204 -0
  408. package/src/mcp/server.js +5 -0
  409. package/src/mcp/server.ts +350 -0
  410. package/src/middleware/auth.ts +342 -0
  411. package/src/middleware/requestId.ts +16 -0
  412. package/src/routes/account.ts +168 -0
  413. package/src/routes/activity.ts +126 -0
  414. package/src/routes/admin.ts +514 -0
  415. package/src/routes/audit.ts +68 -0
  416. package/src/routes/auth.ts +203 -0
  417. package/src/routes/availability.ts +325 -0
  418. package/src/routes/billing.ts +406 -0
  419. package/src/routes/conflicts.ts +131 -0
  420. package/src/routes/holds.ts +437 -0
  421. package/src/routes/mcp.ts +57 -0
  422. package/src/routes/metrics.ts +39 -0
  423. package/src/routes/onboarding.ts +273 -0
  424. package/src/routes/orgs.ts +981 -0
  425. package/src/routes/preferences.ts +132 -0
  426. package/src/routes/session.ts +16 -0
  427. package/src/routes/support.ts +77 -0
  428. package/src/routes/value.ts +186 -0
  429. package/src/routes/waitlist.ts +63 -0
  430. package/src/routes/work.ts +1578 -0
  431. package/src/server.ts +36 -0
  432. package/src/types/index.ts +109 -0
  433. package/tests/integration/activity.route.test.ts +103 -0
  434. package/tests/integration/admin.route.test.ts +143 -0
  435. package/tests/integration/agent-keys.route.test.ts +237 -0
  436. package/tests/integration/availability.route.test.ts +125 -0
  437. package/tests/integration/billing.route.test.ts +393 -0
  438. package/tests/integration/conflicts.route.test.ts +131 -0
  439. package/tests/integration/flows.test.ts +154 -0
  440. package/tests/integration/helpers.ts +134 -0
  441. package/tests/integration/holds.route.test.ts +185 -0
  442. package/tests/integration/metrics.route.test.ts +100 -0
  443. package/tests/integration/onboarding.verify.route.test.ts +163 -0
  444. package/tests/integration/preferences.route.test.ts +53 -0
  445. package/tests/integration/session.route.test.ts +97 -0
  446. package/tests/integration/system.route.test.ts +92 -0
  447. package/tests/integration/value.route.test.ts +235 -0
  448. package/tests/integration/work.route.test.ts +745 -0
  449. package/tests/setup.ts +4 -0
  450. package/tests/smoke.sh +62 -0
  451. package/tests/unit/auth.test.ts +114 -0
  452. package/tests/unit/availability.test.ts +149 -0
  453. package/tests/unit/conflict.test.ts +118 -0
  454. package/tests/unit/env.test.ts +69 -0
  455. package/tests/unit/migrations.test.ts +135 -0
  456. package/tests/unit/request-id.test.ts +37 -0
  457. package/tmp-mobile-agents.png +0 -0
  458. package/tmp-next-mobile.err.log +10 -0
  459. package/tmp-next-mobile.log +5 -0
  460. package/tsconfig.json +16 -0
@@ -0,0 +1,1530 @@
1
+ 'use client';
2
+
3
+ import { useEffect, useState } from 'react';
4
+ import {
5
+ Check,
6
+ Clock3,
7
+ GitBranch,
8
+ ListChecks,
9
+ Lock,
10
+ RadioTower,
11
+ ShieldCheck,
12
+ Zap,
13
+ } from 'lucide-react';
14
+ import ClaudeIcon from '@lobehub/icons/es/Claude/components/Color';
15
+ import CodexIcon from '@lobehub/icons/es/Codex/components/Color';
16
+ import CursorIcon from '@lobehub/icons/es/Cursor/components/Mono';
17
+ import MCPIcon from '@lobehub/icons/es/MCP/components/Mono';
18
+ import VercelIcon from '@lobehub/icons/es/Vercel/components/Mono';
19
+ import type { IconType } from '@lobehub/icons/es/types';
20
+
21
+ type Agent = {
22
+ id: string;
23
+ name: string;
24
+ icon: IconType;
25
+ task: string;
26
+ resource: ResourceLane;
27
+ outcome: 'proceed' | 'skip_run';
28
+ reason: string;
29
+ hue: string;
30
+ };
31
+
32
+ type ScenarioId = 'priority' | 'reservations' | 'history';
33
+ type ResourceLane = 'primary' | 'secondary';
34
+
35
+ type ResourceCopy = {
36
+ title: string;
37
+ idleLabel: string;
38
+ claimedLabel: string;
39
+ };
40
+
41
+ type Scenario = {
42
+ id: ScenarioId;
43
+ title: string;
44
+ body: string;
45
+ heading: string;
46
+ intro: string;
47
+ detail: string;
48
+ eyebrow: string;
49
+ live: string;
50
+ settled: string;
51
+ gateSettled: string;
52
+ resources: Record<ResourceLane, ResourceCopy>;
53
+ agents: Agent[];
54
+ };
55
+
56
+ const scenarios: Scenario[] = [
57
+ {
58
+ id: 'priority',
59
+ title: 'Priority',
60
+ body: 'Deploys, migrations, reviews, and customer-facing work can win deterministically.',
61
+ heading: 'Let the right agent win before work starts.',
62
+ intro:
63
+ 'Several coding agents can wake up with the same repo state and all believe their run is safe. Availsync turns that invisible race into a deterministic decision.',
64
+ detail:
65
+ 'Each agent checks in with the same resource. The gate compares active claims and priority rules, then returns proceed to the winner and skip_run to the rest.',
66
+ eyebrow: 'Priority arbitration',
67
+ live: 'Availsync is comparing priority rules in real time.',
68
+ settled: 'Deploy wins the repo. Lower-priority agents skip cleanly.',
69
+ gateSettled: '1 proceed · 3 skip_run',
70
+ resources: {
71
+ primary: { title: 'owner/repo', idleLabel: 'awaiting claim', claimedLabel: 'claimed' },
72
+ secondary: { title: 'docs-site', idleLabel: 'awaiting claim', claimedLabel: 'claimed' },
73
+ },
74
+ agents: [
75
+ {
76
+ id: 'codex',
77
+ name: 'Codex',
78
+ icon: CodexIcon,
79
+ task: 'Homepage update',
80
+ resource: 'primary',
81
+ outcome: 'skip_run',
82
+ reason: 'Deploy owns repo',
83
+ hue: '#60a5fa',
84
+ },
85
+ {
86
+ id: 'claude',
87
+ name: 'Claude',
88
+ icon: ClaudeIcon,
89
+ task: 'Product catalog',
90
+ resource: 'primary',
91
+ outcome: 'skip_run',
92
+ reason: 'stale context risk',
93
+ hue: '#c084fc',
94
+ },
95
+ {
96
+ id: 'deploy',
97
+ name: 'Deploy',
98
+ icon: VercelIcon,
99
+ task: 'Release window',
100
+ resource: 'primary',
101
+ outcome: 'proceed',
102
+ reason: 'highest priority',
103
+ hue: '#fb923c',
104
+ },
105
+ {
106
+ id: 'mcp',
107
+ name: 'MCP docs',
108
+ icon: MCPIcon,
109
+ task: 'Docs refresh',
110
+ resource: 'secondary',
111
+ outcome: 'proceed',
112
+ reason: 'different resource',
113
+ hue: '#34d399',
114
+ },
115
+ {
116
+ id: 'cursor',
117
+ name: 'Cursor',
118
+ icon: CursorIcon,
119
+ task: 'Dependency cleanup',
120
+ resource: 'primary',
121
+ outcome: 'skip_run',
122
+ reason: 'lower priority',
123
+ hue: '#94a3b8',
124
+ },
125
+ ],
126
+ },
127
+ {
128
+ id: 'reservations',
129
+ title: 'Reservations',
130
+ body: 'Every integrated agent checks the same shared claim state before touching a repo or project.',
131
+ heading: 'Protect repos, projects, and time windows with one claim model.',
132
+ intro:
133
+ 'A deploy slot, repo, or project can be held before an agent acts. Other agents see that reservation instead of discovering the conflict after they have already started.',
134
+ detail:
135
+ 'Use repo-level claims for safety, or reserve narrower project and time-window resources when streams are independent.',
136
+ eyebrow: 'Shared reservations',
137
+ live: 'Availsync is checking active leases before agents start.',
138
+ settled: 'The protected slot is held. Independent work can continue.',
139
+ gateSettled: 'slot held · repo free',
140
+ resources: {
141
+ primary: { title: 'deploy-window 10:00', idleLabel: 'checking slot', claimedLabel: 'held' },
142
+ secondary: { title: 'owner/docs', idleLabel: 'checking repo', claimedLabel: 'claimed' },
143
+ },
144
+ agents: [
145
+ {
146
+ id: 'release-bot',
147
+ name: 'Release bot',
148
+ icon: VercelIcon,
149
+ task: 'Book deploy slot',
150
+ resource: 'primary',
151
+ outcome: 'proceed',
152
+ reason: 'slot available',
153
+ hue: '#fb923c',
154
+ },
155
+ {
156
+ id: 'migration',
157
+ name: 'Migration',
158
+ icon: CodexIcon,
159
+ task: 'Needs 10:00 window',
160
+ resource: 'primary',
161
+ outcome: 'skip_run',
162
+ reason: 'slot already held',
163
+ hue: '#60a5fa',
164
+ },
165
+ {
166
+ id: 'support',
167
+ name: 'Support bot',
168
+ icon: ClaudeIcon,
169
+ task: 'Customer hotfix',
170
+ resource: 'primary',
171
+ outcome: 'skip_run',
172
+ reason: 'release protected',
173
+ hue: '#c084fc',
174
+ },
175
+ {
176
+ id: 'docs',
177
+ name: 'Docs agent',
178
+ icon: MCPIcon,
179
+ task: 'Update runbook',
180
+ resource: 'secondary',
181
+ outcome: 'proceed',
182
+ reason: 'independent repo',
183
+ hue: '#34d399',
184
+ },
185
+ {
186
+ id: 'cleanup',
187
+ name: 'Cron cleanup',
188
+ icon: CursorIcon,
189
+ task: 'Dependency sweep',
190
+ resource: 'primary',
191
+ outcome: 'skip_run',
192
+ reason: 'wait for release',
193
+ hue: '#94a3b8',
194
+ },
195
+ ],
196
+ },
197
+ {
198
+ id: 'history',
199
+ title: 'History',
200
+ body: 'Checks, claims, skip_run decisions, extensions, releases, and actors stay reviewable.',
201
+ heading: 'See what happened after your agents finish.',
202
+ intro:
203
+ 'Coordination is only useful if you can review the decisions later. Availsync records checks, claims, blocked runs, extensions, releases, and actors as runtime history.',
204
+ detail:
205
+ 'Activity shows live agent traffic. Audit gives you the governance trail when you need to understand why a run proceeded or skipped.',
206
+ eyebrow: 'Runtime history',
207
+ live: 'Availsync is turning runtime decisions into reviewable events.',
208
+ settled: 'Every decision is captured for Activity and Audit review.',
209
+ gateSettled: 'activity · audit',
210
+ resources: {
211
+ primary: { title: 'Activity log', idleLabel: 'waiting events', claimedLabel: 'recorded' },
212
+ secondary: { title: 'Audit trail', idleLabel: 'waiting events', claimedLabel: 'recorded' },
213
+ },
214
+ agents: [
215
+ {
216
+ id: 'check',
217
+ name: 'Check',
218
+ icon: CodexIcon,
219
+ task: 'work/run/start',
220
+ resource: 'primary',
221
+ outcome: 'proceed',
222
+ reason: 'available',
223
+ hue: '#60a5fa',
224
+ },
225
+ {
226
+ id: 'claim',
227
+ name: 'Claim',
228
+ icon: VercelIcon,
229
+ task: 'repo claimed',
230
+ resource: 'primary',
231
+ outcome: 'proceed',
232
+ reason: 'lease active',
233
+ hue: '#fb923c',
234
+ },
235
+ {
236
+ id: 'skip',
237
+ name: 'Skip',
238
+ icon: ClaudeIcon,
239
+ task: 'blocked run',
240
+ resource: 'primary',
241
+ outcome: 'skip_run',
242
+ reason: 'conflict saved',
243
+ hue: '#f87171',
244
+ },
245
+ {
246
+ id: 'extend',
247
+ name: 'Extend',
248
+ icon: CursorIcon,
249
+ task: 'lease renewed',
250
+ resource: 'secondary',
251
+ outcome: 'proceed',
252
+ reason: 'still working',
253
+ hue: '#94a3b8',
254
+ },
255
+ {
256
+ id: 'release',
257
+ name: 'Release',
258
+ icon: MCPIcon,
259
+ task: 'work finished',
260
+ resource: 'secondary',
261
+ outcome: 'proceed',
262
+ reason: 'resource freed',
263
+ hue: '#34d399',
264
+ },
265
+ ],
266
+ },
267
+ ];
268
+
269
+ const defaultScenario = scenarios[0];
270
+
271
+ // --- Coordinate system (SVG viewBox) ---
272
+ const VIEW_W = 820;
273
+ const VIEW_H = 560;
274
+
275
+ const AGENT_X = 20;
276
+ const AGENT_W = 190;
277
+ const AGENT_H = 58;
278
+ const AGENT_PITCH = 78;
279
+ const AGENT_COUNT = 5;
280
+ const AGENT_FIRST_TOP = (VIEW_H - (AGENT_COUNT - 1) * AGENT_PITCH - AGENT_H) / 2;
281
+ const agentTop = (i: number) => AGENT_FIRST_TOP + i * AGENT_PITCH;
282
+ const agentCenterY = (i: number) => agentTop(i) + AGENT_H / 2;
283
+ const AGENT_RIGHT_EDGE = AGENT_X + AGENT_W;
284
+
285
+ const GATE_CX = 410;
286
+ const GATE_CY = VIEW_H / 2;
287
+ const GATE_R = 54;
288
+
289
+ const RES_W = 190;
290
+ const RES_H = 86;
291
+ const RES_X = VIEW_W - RES_W - 20;
292
+ const RES_PRIMARY_TOP = 140;
293
+ const RES_SECONDARY_TOP = 336;
294
+ const resourceTop = (r: ResourceLane) => (r === 'primary' ? RES_PRIMARY_TOP : RES_SECONDARY_TOP);
295
+ const resourceCenterY = (r: ResourceLane) => resourceTop(r) + RES_H / 2;
296
+
297
+ function pathAgentToGate(i: number): string {
298
+ const sx = AGENT_RIGHT_EDGE;
299
+ const sy = agentCenterY(i);
300
+ const ex = GATE_CX - GATE_R;
301
+ const ey = GATE_CY;
302
+ const dx = ex - sx;
303
+ return `M ${sx} ${sy} C ${sx + dx * 0.55} ${sy}, ${ex - dx * 0.45} ${ey}, ${ex} ${ey}`;
304
+ }
305
+
306
+ function pathGateToResource(r: ResourceLane): string {
307
+ const sx = GATE_CX + GATE_R;
308
+ const sy = GATE_CY;
309
+ const ex = RES_X;
310
+ const ey = resourceCenterY(r);
311
+ const dx = ex - sx;
312
+ return `M ${sx} ${sy} C ${sx + dx * 0.5} ${sy}, ${ex - dx * 0.5} ${ey}, ${ex} ${ey}`;
313
+ }
314
+
315
+ // --- Scenario phases ---
316
+ type Phase = 'idle' | 'request' | 'decide' | 'route' | 'settled';
317
+
318
+ const PHASE_ORDER: Phase[] = ['idle', 'request', 'decide', 'route', 'settled'];
319
+ const PHASE_MS: Record<Phase, number> = {
320
+ idle: 900,
321
+ request: 2300,
322
+ decide: 1700,
323
+ route: 2300,
324
+ settled: 2700,
325
+ };
326
+ const LOOP_MS = PHASE_ORDER.reduce((total, p) => total + PHASE_MS[p], 0);
327
+ const LOOP_SECONDS = LOOP_MS / 1000;
328
+
329
+ function useScenario(reduced: boolean) {
330
+ const [phase, setPhase] = useState<Phase>(reduced ? 'settled' : 'idle');
331
+ const [cycle, setCycle] = useState(0);
332
+
333
+ useEffect(() => {
334
+ if (reduced) {
335
+ setPhase('settled');
336
+ return;
337
+ }
338
+ let cancelled = false;
339
+ let timer: number | undefined;
340
+ let pi = 0;
341
+ let ci = 0;
342
+
343
+ const tick = () => {
344
+ if (cancelled) return;
345
+ const p = PHASE_ORDER[pi];
346
+ setPhase(p);
347
+ setCycle(ci);
348
+ timer = window.setTimeout(() => {
349
+ pi += 1;
350
+ if (pi >= PHASE_ORDER.length) {
351
+ pi = 0;
352
+ ci += 1;
353
+ }
354
+ tick();
355
+ }, PHASE_MS[p]);
356
+ };
357
+
358
+ tick();
359
+ return () => {
360
+ cancelled = true;
361
+ if (timer) window.clearTimeout(timer);
362
+ };
363
+ }, [reduced]);
364
+
365
+ return { phase, cycle };
366
+ }
367
+
368
+ // --- Packet that travels along an SVG path ---
369
+ function Packet({
370
+ pathId,
371
+ start,
372
+ end,
373
+ visibleUntil,
374
+ color,
375
+ size = 5,
376
+ reverse = false,
377
+ }: {
378
+ pathId: string;
379
+ start: number;
380
+ end: number;
381
+ visibleUntil?: number;
382
+ color: string;
383
+ size?: number;
384
+ reverse?: boolean;
385
+ }) {
386
+ const fadeIn = Math.min(start + 0.025, end);
387
+ const holdUntil = Math.max(end, Math.min(visibleUntil ?? end, 0.965));
388
+ const fadeOut = Math.min(holdUntil + 0.04, 0.985);
389
+ const keyTimes = `0;${start};${end};1`;
390
+ const keyPoints = reverse ? '1;1;0;0' : '0;0;1;1';
391
+ const opacityTimes = `0;${start};${fadeIn};${holdUntil};${fadeOut};1`;
392
+
393
+ return (
394
+ <g style={{ pointerEvents: 'none', willChange: 'opacity' }}>
395
+ <circle r={size + 5} fill={color} opacity={0.18}>
396
+ <animateMotion
397
+ dur={`${LOOP_SECONDS}s`}
398
+ repeatCount="indefinite"
399
+ calcMode="linear"
400
+ keyTimes={keyTimes}
401
+ keyPoints={keyPoints}
402
+ >
403
+ <mpath href={`#${pathId}`} />
404
+ </animateMotion>
405
+ <animate
406
+ attributeName="opacity"
407
+ dur={`${LOOP_SECONDS}s`}
408
+ repeatCount="indefinite"
409
+ keyTimes={opacityTimes}
410
+ values="0;0;0.18;0.18;0;0"
411
+ />
412
+ </circle>
413
+ <circle r={size} fill={color}>
414
+ <animateMotion
415
+ dur={`${LOOP_SECONDS}s`}
416
+ repeatCount="indefinite"
417
+ calcMode="linear"
418
+ keyTimes={keyTimes}
419
+ keyPoints={keyPoints}
420
+ >
421
+ <mpath href={`#${pathId}`} />
422
+ </animateMotion>
423
+ <animate
424
+ attributeName="opacity"
425
+ dur={`${LOOP_SECONDS}s`}
426
+ repeatCount="indefinite"
427
+ keyTimes={opacityTimes}
428
+ values="0;0;1;1;0;0"
429
+ />
430
+ </circle>
431
+ </g>
432
+ );
433
+ }
434
+
435
+ // --- Gate node ---
436
+ function GateNode({ phase, scenario }: { phase: Phase; scenario: Scenario }) {
437
+ const deciding = phase === 'decide' || phase === 'route';
438
+ const active = phase === 'request' || phase === 'decide' || phase === 'route';
439
+
440
+ return (
441
+ <g>
442
+ {/* Pulsing rings (group is translated to gate center so we can scale around origin (0,0)) */}
443
+ {active && (
444
+ <g>
445
+ <circle
446
+ cx={GATE_CX}
447
+ cy={GATE_CY}
448
+ r={GATE_R}
449
+ fill="none"
450
+ stroke="rgba(125,140,240,0.55)"
451
+ strokeWidth={1}
452
+ >
453
+ <animate attributeName="r" values={`${GATE_R - 4};${GATE_R + 46}`} dur="1.9s" repeatCount="indefinite" />
454
+ <animate attributeName="opacity" values="0.7;0" dur="1.9s" repeatCount="indefinite" />
455
+ </circle>
456
+ <circle
457
+ cx={GATE_CX}
458
+ cy={GATE_CY}
459
+ r={GATE_R}
460
+ fill="none"
461
+ stroke="rgba(125,140,240,0.35)"
462
+ strokeWidth={1}
463
+ >
464
+ <animate attributeName="r" values={`${GATE_R - 4};${GATE_R + 46}`} dur="1.9s" begin="0.95s" repeatCount="indefinite" />
465
+ <animate attributeName="opacity" values="0.55;0" dur="1.9s" begin="0.95s" repeatCount="indefinite" />
466
+ </circle>
467
+ </g>
468
+ )}
469
+
470
+ {/* Gate body */}
471
+ <circle
472
+ cx={GATE_CX}
473
+ cy={GATE_CY}
474
+ r={GATE_R}
475
+ fill="url(#gate-grad)"
476
+ stroke={deciding ? '#a5b4fc' : 'rgba(125,140,240,0.55)'}
477
+ strokeWidth={deciding ? 2 : 1}
478
+ style={{
479
+ filter: deciding
480
+ ? 'drop-shadow(0 0 26px rgba(125,140,240,0.65))'
481
+ : 'drop-shadow(0 0 10px rgba(94,106,210,0.3))',
482
+ transition: 'filter 450ms ease, stroke 450ms ease',
483
+ }}
484
+ />
485
+
486
+ {/* Scanner sweep. Native SVG rotation keeps the pivot exact inside the clipped gate. */}
487
+ {deciding && (
488
+ <g clipPath="url(#gate-inner-clip)">
489
+ <g transform={`translate(${GATE_CX} ${GATE_CY})`}>
490
+ <g>
491
+ <animateTransform
492
+ attributeName="transform"
493
+ type="rotate"
494
+ from="0"
495
+ to="360"
496
+ dur="1.35s"
497
+ repeatCount="indefinite"
498
+ />
499
+ <path
500
+ d={`M 0 0 L ${GATE_R - 2} -14 A ${GATE_R - 2} ${GATE_R - 2} 0 0 1 ${GATE_R - 2} 14 Z`}
501
+ fill="url(#sweep-grad)"
502
+ />
503
+ <line
504
+ x1="6"
505
+ y1="0"
506
+ x2={GATE_R - 5}
507
+ y2="0"
508
+ stroke="rgba(199,210,254,0.55)"
509
+ strokeLinecap="round"
510
+ strokeWidth="1.2"
511
+ />
512
+ </g>
513
+ </g>
514
+ </g>
515
+ )}
516
+
517
+ {/* Shield icon */}
518
+ <circle
519
+ cx={GATE_CX}
520
+ cy={GATE_CY}
521
+ r={16}
522
+ fill="rgba(9,11,18,0.42)"
523
+ stroke="rgba(255,255,255,0.08)"
524
+ strokeWidth={1}
525
+ />
526
+ <g transform={`translate(${GATE_CX - 13} ${GATE_CY - 14})`}>
527
+ <ShieldCheck color={deciding ? '#c7d2fe' : '#a5b4fc'} size={26} />
528
+ </g>
529
+
530
+ {/* Label */}
531
+ <text
532
+ x={GATE_CX}
533
+ y={GATE_CY + GATE_R + 26}
534
+ textAnchor="middle"
535
+ fill="#a3a3ad"
536
+ fontSize={10}
537
+ letterSpacing={1.4}
538
+ fontFamily="system-ui,-apple-system,sans-serif"
539
+ >
540
+ AVAILSYNC · GATE
541
+ </text>
542
+ <text
543
+ x={GATE_CX}
544
+ y={GATE_CY + GATE_R + 43}
545
+ textAnchor="middle"
546
+ fill={deciding ? '#c7d2fe' : '#7a7f95'}
547
+ fontSize={11}
548
+ fontFamily="ui-monospace,SFMono-Regular,monospace"
549
+ >
550
+ {phase === 'idle' && 'awaiting check-ins'}
551
+ {phase === 'request' && 'receiving claims'}
552
+ {phase === 'decide' && 'evaluating'}
553
+ {phase === 'route' && 'routing decisions'}
554
+ {phase === 'settled' && scenario.gateSettled}
555
+ </text>
556
+ </g>
557
+ );
558
+ }
559
+
560
+ // --- Agent card (inside SVG via foreignObject) ---
561
+ function AgentCard({
562
+ agent,
563
+ index,
564
+ phase,
565
+ }: {
566
+ agent: Agent;
567
+ index: number;
568
+ phase: Phase;
569
+ }) {
570
+ const Icon = agent.icon;
571
+ const verdictShown = phase === 'route' || phase === 'settled';
572
+ const settled = phase === 'settled';
573
+ const dimmed = settled && agent.outcome === 'skip_run';
574
+ const wakingUp = phase === 'request' || phase === 'decide';
575
+
576
+ return (
577
+ <foreignObject x={AGENT_X} y={agentTop(index)} width={AGENT_W} height={AGENT_H}>
578
+ <div
579
+ style={{
580
+ width: '100%',
581
+ height: '100%',
582
+ boxSizing: 'border-box',
583
+ padding: '8px 10px',
584
+ display: 'flex',
585
+ alignItems: 'center',
586
+ gap: 9,
587
+ borderRadius: 8,
588
+ border: `1px solid ${dimmed ? 'rgba(255,255,255,0.08)' : `${agent.hue}40`}`,
589
+ background: dimmed
590
+ ? 'rgba(20,22,32,0.6)'
591
+ : `linear-gradient(135deg, ${agent.hue}14, rgba(18,20,30,0.92))`,
592
+ boxShadow: wakingUp ? `0 0 18px ${agent.hue}30` : 'none',
593
+ transition: 'all 500ms cubic-bezier(0.4,0,0.2,1)',
594
+ color: '#e5e7eb',
595
+ fontFamily: 'system-ui,-apple-system,sans-serif',
596
+ opacity: dimmed ? 0.65 : 1,
597
+ }}
598
+ >
599
+ <div
600
+ style={{
601
+ width: 32,
602
+ height: 32,
603
+ flexShrink: 0,
604
+ borderRadius: 6,
605
+ display: 'flex',
606
+ alignItems: 'center',
607
+ justifyContent: 'center',
608
+ background: `${agent.hue}1f`,
609
+ border: `1px solid ${agent.hue}55`,
610
+ }}
611
+ >
612
+ <Icon size={18} />
613
+ </div>
614
+ <div style={{ flex: 1, minWidth: 0 }}>
615
+ <div style={{ fontSize: 12, fontWeight: 600, lineHeight: 1.2 }}>{agent.name}</div>
616
+ <div
617
+ style={{
618
+ fontSize: 10.5,
619
+ color: '#9ca3af',
620
+ whiteSpace: 'nowrap',
621
+ overflow: 'hidden',
622
+ textOverflow: 'ellipsis',
623
+ marginTop: 1,
624
+ }}
625
+ >
626
+ {agent.task}
627
+ </div>
628
+ </div>
629
+ {verdictShown && (
630
+ <div
631
+ style={{
632
+ fontSize: 9.5,
633
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
634
+ padding: '3px 6px',
635
+ borderRadius: 4,
636
+ flexShrink: 0,
637
+ background:
638
+ agent.outcome === 'proceed'
639
+ ? 'rgba(52,211,153,0.18)'
640
+ : 'rgba(255,255,255,0.06)',
641
+ color: agent.outcome === 'proceed' ? '#34d399' : '#94a3b8',
642
+ border:
643
+ agent.outcome === 'proceed'
644
+ ? '1px solid rgba(52,211,153,0.35)'
645
+ : '1px solid rgba(255,255,255,0.08)',
646
+ }}
647
+ >
648
+ {agent.outcome}
649
+ </div>
650
+ )}
651
+ </div>
652
+ </foreignObject>
653
+ );
654
+ }
655
+
656
+ // --- Resource card ---
657
+ function ResourceCard({
658
+ resource,
659
+ phase,
660
+ claimer,
661
+ copy,
662
+ }: {
663
+ resource: ResourceLane;
664
+ phase: Phase;
665
+ claimer: Agent | undefined;
666
+ copy: ResourceCopy;
667
+ }) {
668
+ const claimed = (phase === 'route' || phase === 'settled') && claimer !== undefined;
669
+ const ClaimerIcon = claimer?.icon;
670
+ const accent = claimer?.hue ?? '#34d399';
671
+
672
+ return (
673
+ <foreignObject x={RES_X} y={resourceTop(resource)} width={RES_W} height={RES_H}>
674
+ <div
675
+ style={{
676
+ width: '100%',
677
+ height: '100%',
678
+ boxSizing: 'border-box',
679
+ padding: 12,
680
+ borderRadius: 8,
681
+ border: claimed
682
+ ? `1px solid ${accent}55`
683
+ : '1px solid rgba(255,255,255,0.1)',
684
+ background: claimed
685
+ ? `linear-gradient(135deg, ${accent}14, rgba(16,18,26,0.92))`
686
+ : 'rgba(16,18,26,0.7)',
687
+ boxShadow: claimed ? `0 0 24px ${accent}26` : 'none',
688
+ transition: 'all 500ms cubic-bezier(0.4,0,0.2,1)',
689
+ color: '#e5e7eb',
690
+ fontFamily: 'system-ui,-apple-system,sans-serif',
691
+ display: 'flex',
692
+ flexDirection: 'column',
693
+ gap: 8,
694
+ }}
695
+ >
696
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
697
+ <div style={{ display: 'flex', alignItems: 'center', gap: 6 }}>
698
+ <GitBranch size={13} color="#9ca3af" />
699
+ <span
700
+ style={{
701
+ fontSize: 12.5,
702
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
703
+ color: '#e5e7eb',
704
+ }}
705
+ >
706
+ {copy.title}
707
+ </span>
708
+ </div>
709
+ {claimed ? (
710
+ <div
711
+ style={{
712
+ display: 'flex',
713
+ alignItems: 'center',
714
+ gap: 4,
715
+ fontSize: 10,
716
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
717
+ color: accent,
718
+ padding: '2px 6px',
719
+ borderRadius: 4,
720
+ background: `${accent}1a`,
721
+ border: `1px solid ${accent}40`,
722
+ }}
723
+ >
724
+ <Lock size={10} />
725
+ <span>{copy.claimedLabel}</span>
726
+ </div>
727
+ ) : (
728
+ <span
729
+ style={{
730
+ fontSize: 10,
731
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
732
+ color: '#6b7280',
733
+ padding: '2px 6px',
734
+ borderRadius: 4,
735
+ border: '1px solid rgba(255,255,255,0.08)',
736
+ }}
737
+ >
738
+ free
739
+ </span>
740
+ )}
741
+ </div>
742
+ <div style={{ display: 'flex', alignItems: 'center', gap: 8, minHeight: 22 }}>
743
+ {claimed && ClaimerIcon ? (
744
+ <>
745
+ <div
746
+ style={{
747
+ width: 22,
748
+ height: 22,
749
+ borderRadius: 4,
750
+ display: 'flex',
751
+ alignItems: 'center',
752
+ justifyContent: 'center',
753
+ background: `${accent}1f`,
754
+ border: `1px solid ${accent}55`,
755
+ }}
756
+ >
757
+ <ClaimerIcon size={13} />
758
+ </div>
759
+ <div style={{ display: 'flex', flexDirection: 'column', minWidth: 0 }}>
760
+ <span style={{ fontSize: 10.5, color: '#d1d5db', lineHeight: 1.2 }}>
761
+ {claimer?.task}
762
+ </span>
763
+ <span
764
+ style={{
765
+ fontSize: 9.5,
766
+ color: '#9ca3af',
767
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
768
+ marginTop: 1,
769
+ }}
770
+ >
771
+ by {claimer?.name}
772
+ </span>
773
+ </div>
774
+ </>
775
+ ) : (
776
+ <span style={{ fontSize: 10.5, color: '#6b7280' }}>{copy.idleLabel}</span>
777
+ )}
778
+ </div>
779
+ </div>
780
+ </foreignObject>
781
+ );
782
+ }
783
+
784
+ function ScenarioFlowLayer({
785
+ scenario,
786
+ phase,
787
+ primaryClaimer,
788
+ secondaryClaimer,
789
+ }: {
790
+ scenario: Scenario;
791
+ phase: Phase;
792
+ primaryClaimer: Agent | undefined;
793
+ secondaryClaimer: Agent | undefined;
794
+ }) {
795
+ const routed = phase === 'route' || phase === 'settled';
796
+
797
+ if (scenario.id === 'reservations') {
798
+ const slotHeld = routed && primaryClaimer !== undefined;
799
+ const docsHeld = routed && secondaryClaimer !== undefined;
800
+
801
+ return (
802
+ <g>
803
+ <rect
804
+ x={RES_X - 16}
805
+ y={RES_PRIMARY_TOP - 18}
806
+ width={RES_W + 32}
807
+ height={RES_H + 36}
808
+ rx={14}
809
+ fill="none"
810
+ stroke={slotHeld ? 'rgba(251,146,60,0.58)' : 'rgba(251,146,60,0.16)'}
811
+ strokeWidth={slotHeld ? 1.6 : 1}
812
+ strokeDasharray="5 7"
813
+ opacity={phase === 'idle' ? 0.45 : 1}
814
+ >
815
+ <animate
816
+ attributeName="stroke-dashoffset"
817
+ from="0"
818
+ to="-48"
819
+ dur="4s"
820
+ repeatCount="indefinite"
821
+ />
822
+ </rect>
823
+
824
+ {slotHeld && (
825
+ <g>
826
+ <circle cx={RES_X - 16} cy={resourceCenterY('primary')} r="4" fill="#fb923c">
827
+ <animate
828
+ attributeName="r"
829
+ values="4;10;4"
830
+ dur="1.7s"
831
+ repeatCount="indefinite"
832
+ />
833
+ <animate
834
+ attributeName="opacity"
835
+ values="1;0.15;1"
836
+ dur="1.7s"
837
+ repeatCount="indefinite"
838
+ />
839
+ </circle>
840
+ <text
841
+ x={RES_X + RES_W / 2}
842
+ y={RES_PRIMARY_TOP - 28}
843
+ textAnchor="middle"
844
+ fill="#fed7aa"
845
+ fontSize={10}
846
+ fontFamily="ui-monospace,SFMono-Regular,monospace"
847
+ >
848
+ lease active until 10:30
849
+ </text>
850
+ </g>
851
+ )}
852
+
853
+ <foreignObject x={252} y={398} width={318} height={126}>
854
+ <div
855
+ style={{
856
+ width: '100%',
857
+ height: '100%',
858
+ boxSizing: 'border-box',
859
+ borderRadius: 10,
860
+ border: '1px solid rgba(251,146,60,0.28)',
861
+ background:
862
+ 'linear-gradient(135deg, rgba(251,146,60,0.12), rgba(14,16,24,0.92))',
863
+ boxShadow: slotHeld ? '0 0 28px rgba(251,146,60,0.12)' : 'none',
864
+ padding: '11px 12px',
865
+ fontFamily: 'system-ui,-apple-system,sans-serif',
866
+ color: '#e5e7eb',
867
+ transition: 'box-shadow 500ms ease',
868
+ }}
869
+ >
870
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
871
+ <span
872
+ style={{
873
+ fontSize: 10,
874
+ letterSpacing: 1.2,
875
+ textTransform: 'uppercase',
876
+ color: '#fed7aa',
877
+ }}
878
+ >
879
+ Reservation ledger
880
+ </span>
881
+ <span
882
+ style={{
883
+ borderRadius: 999,
884
+ border: '1px solid rgba(251,146,60,0.35)',
885
+ padding: '2px 7px',
886
+ fontSize: 10,
887
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
888
+ color: slotHeld ? '#fb923c' : '#787b8d',
889
+ }}
890
+ >
891
+ {slotHeld ? 'slot held' : 'checking'}
892
+ </span>
893
+ </div>
894
+
895
+ <div style={{ marginTop: 10, display: 'grid', gap: 7 }}>
896
+ {[
897
+ {
898
+ label: 'deploy-window 10:00',
899
+ value: slotHeld ? `held by ${primaryClaimer?.name}` : 'open',
900
+ color: '#fb923c',
901
+ active: slotHeld,
902
+ },
903
+ {
904
+ label: 'overlapping deploy asks',
905
+ value: slotHeld ? 'return skip_run' : 'waiting',
906
+ color: '#f87171',
907
+ active: slotHeld,
908
+ },
909
+ {
910
+ label: 'owner/docs',
911
+ value: docsHeld ? `proceed for ${secondaryClaimer?.name}` : 'independent',
912
+ color: '#34d399',
913
+ active: docsHeld,
914
+ },
915
+ ].map((row) => (
916
+ <div
917
+ key={row.label}
918
+ style={{
919
+ display: 'grid',
920
+ gridTemplateColumns: '9px 1fr auto',
921
+ alignItems: 'center',
922
+ gap: 8,
923
+ fontSize: 10.5,
924
+ }}
925
+ >
926
+ <span
927
+ style={{
928
+ width: 7,
929
+ height: 7,
930
+ borderRadius: 999,
931
+ background: row.active ? row.color : 'rgba(255,255,255,0.14)',
932
+ boxShadow: row.active ? `0 0 12px ${row.color}66` : 'none',
933
+ }}
934
+ />
935
+ <span style={{ color: '#cbd5e1', overflow: 'hidden', textOverflow: 'ellipsis' }}>
936
+ {row.label}
937
+ </span>
938
+ <span
939
+ style={{
940
+ color: row.active ? row.color : '#7a7f95',
941
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
942
+ whiteSpace: 'nowrap',
943
+ }}
944
+ >
945
+ {row.value}
946
+ </span>
947
+ </div>
948
+ ))}
949
+ </div>
950
+ </div>
951
+ </foreignObject>
952
+ </g>
953
+ );
954
+ }
955
+
956
+ if (scenario.id === 'history') {
957
+ const events = [
958
+ { phase: 'request', label: 'check', detail: 'work/run/start', color: '#60a5fa' },
959
+ { phase: 'route', label: 'claim', detail: 'repo claimed', color: '#34d399' },
960
+ { phase: 'route', label: 'skip_run', detail: 'blocked overlap', color: '#f87171' },
961
+ { phase: 'settled', label: 'release', detail: 'resource freed', color: '#a5b4fc' },
962
+ ] as const;
963
+ const phaseRank: Record<Phase, number> = {
964
+ idle: 0,
965
+ request: 1,
966
+ decide: 1,
967
+ route: 2,
968
+ settled: 3,
969
+ };
970
+
971
+ return (
972
+ <g>
973
+ <foreignObject x={245} y={388} width={332} height={138}>
974
+ <div
975
+ style={{
976
+ width: '100%',
977
+ height: '100%',
978
+ boxSizing: 'border-box',
979
+ borderRadius: 10,
980
+ border: '1px solid rgba(52,211,153,0.26)',
981
+ background:
982
+ 'linear-gradient(135deg, rgba(52,211,153,0.1), rgba(14,16,24,0.94))',
983
+ boxShadow: phase === 'settled' ? '0 0 28px rgba(52,211,153,0.12)' : 'none',
984
+ padding: '11px 12px',
985
+ fontFamily: 'system-ui,-apple-system,sans-serif',
986
+ color: '#e5e7eb',
987
+ }}
988
+ >
989
+ <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
990
+ <span
991
+ style={{
992
+ fontSize: 10,
993
+ letterSpacing: 1.2,
994
+ textTransform: 'uppercase',
995
+ color: '#86efac',
996
+ }}
997
+ >
998
+ Activity stream
999
+ </span>
1000
+ <span
1001
+ style={{
1002
+ color: phase === 'settled' ? '#34d399' : '#7a7f95',
1003
+ fontSize: 10,
1004
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
1005
+ }}
1006
+ >
1007
+ {phase === 'settled' ? 'reviewable' : 'recording'}
1008
+ </span>
1009
+ </div>
1010
+
1011
+ <div style={{ marginTop: 10, display: 'grid', gap: 6 }}>
1012
+ {events.map((event, index) => {
1013
+ const visible = phaseRank[phase] >= phaseRank[event.phase as Phase];
1014
+ return (
1015
+ <div
1016
+ key={event.label}
1017
+ style={{
1018
+ display: 'grid',
1019
+ gridTemplateColumns: '42px 1fr auto',
1020
+ alignItems: 'center',
1021
+ gap: 8,
1022
+ borderRadius: 6,
1023
+ border: visible
1024
+ ? `1px solid ${event.color}35`
1025
+ : '1px solid rgba(255,255,255,0.06)',
1026
+ background: visible ? `${event.color}12` : 'rgba(255,255,255,0.025)',
1027
+ padding: '5px 7px',
1028
+ opacity: visible ? 1 : 0.42,
1029
+ transform: visible ? 'translateX(0)' : 'translateX(-6px)',
1030
+ transition: `opacity 450ms ease ${index * 80}ms, transform 450ms ease ${index * 80}ms, border-color 450ms ease`,
1031
+ }}
1032
+ >
1033
+ <span
1034
+ style={{
1035
+ color: visible ? event.color : '#7a7f95',
1036
+ fontSize: 9.5,
1037
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
1038
+ }}
1039
+ >
1040
+ {String(index + 1).padStart(2, '0')}
1041
+ </span>
1042
+ <span
1043
+ style={{
1044
+ color: visible ? '#e5e7eb' : '#8b91a3',
1045
+ fontSize: 10.5,
1046
+ whiteSpace: 'nowrap',
1047
+ overflow: 'hidden',
1048
+ textOverflow: 'ellipsis',
1049
+ }}
1050
+ >
1051
+ {event.detail}
1052
+ </span>
1053
+ <span
1054
+ style={{
1055
+ color: visible ? event.color : '#7a7f95',
1056
+ fontSize: 9.5,
1057
+ fontFamily: 'ui-monospace,SFMono-Regular,monospace',
1058
+ }}
1059
+ >
1060
+ {event.label}
1061
+ </span>
1062
+ </div>
1063
+ );
1064
+ })}
1065
+ </div>
1066
+ </div>
1067
+ </foreignObject>
1068
+ </g>
1069
+ );
1070
+ }
1071
+
1072
+ return null;
1073
+ }
1074
+
1075
+ // --- Connector path (the static dim line + dynamic highlight) ---
1076
+ function ConnectorPath({
1077
+ d,
1078
+ highlighted,
1079
+ color,
1080
+ }: {
1081
+ d: string;
1082
+ highlighted: boolean;
1083
+ color: string;
1084
+ }) {
1085
+ return (
1086
+ <g>
1087
+ <path
1088
+ d={d}
1089
+ fill="none"
1090
+ stroke="rgba(125,140,240,0.13)"
1091
+ strokeWidth={1}
1092
+ strokeDasharray="3 4"
1093
+ />
1094
+ <path
1095
+ d={d}
1096
+ fill="none"
1097
+ stroke={color}
1098
+ strokeWidth={1.4}
1099
+ strokeLinecap="round"
1100
+ opacity={highlighted ? 0.76 : 0}
1101
+ strokeDasharray="8 10"
1102
+ style={{
1103
+ transition: 'opacity 650ms cubic-bezier(0.25,1,0.5,1)',
1104
+ }}
1105
+ >
1106
+ <animate
1107
+ attributeName="stroke-dashoffset"
1108
+ from="0"
1109
+ to="-54"
1110
+ dur="2.2s"
1111
+ repeatCount="indefinite"
1112
+ />
1113
+ </path>
1114
+ </g>
1115
+ );
1116
+ }
1117
+
1118
+ // --- Background grid ---
1119
+ function BackgroundGrid() {
1120
+ return (
1121
+ <g opacity={0.6}>
1122
+ <defs>
1123
+ <pattern id="grid-dots" width="32" height="32" patternUnits="userSpaceOnUse">
1124
+ <circle cx="1" cy="1" r="0.7" fill="rgba(125,140,240,0.12)" />
1125
+ </pattern>
1126
+ <radialGradient id="bg-glow" cx="50%" cy="50%" r="55%">
1127
+ <stop offset="0%" stopColor="rgba(94,106,210,0.18)" />
1128
+ <stop offset="60%" stopColor="rgba(94,106,210,0.04)" />
1129
+ <stop offset="100%" stopColor="rgba(0,0,0,0)" />
1130
+ </radialGradient>
1131
+ </defs>
1132
+ <rect x="0" y="0" width={VIEW_W} height={VIEW_H} fill="url(#grid-dots)" />
1133
+ <rect x="0" y="0" width={VIEW_W} height={VIEW_H} fill="url(#bg-glow)" />
1134
+ </g>
1135
+ );
1136
+ }
1137
+
1138
+ // --- Main animated stage ---
1139
+ function CoordinationStage({ phase, scenario }: { phase: Phase; scenario: Scenario }) {
1140
+ const primaryClaimer = scenario.agents.find(
1141
+ (a) => a.resource === 'primary' && a.outcome === 'proceed',
1142
+ );
1143
+ const secondaryClaimer = scenario.agents.find(
1144
+ (a) => a.resource === 'secondary' && a.outcome === 'proceed',
1145
+ );
1146
+
1147
+ return (
1148
+ <svg
1149
+ viewBox={`0 0 ${VIEW_W} ${VIEW_H}`}
1150
+ className="block h-auto w-full"
1151
+ role="img"
1152
+ aria-label="Availsync coordinates agent claims before they touch shared resources"
1153
+ >
1154
+ <defs>
1155
+ <radialGradient id="gate-grad" cx="50%" cy="50%" r="50%">
1156
+ <stop offset="0%" stopColor="rgba(165,180,252,0.45)" />
1157
+ <stop offset="55%" stopColor="rgba(78,84,138,0.55)" />
1158
+ <stop offset="100%" stopColor="rgba(28,30,46,0.95)" />
1159
+ </radialGradient>
1160
+ <clipPath id="gate-inner-clip">
1161
+ <circle cx={GATE_CX} cy={GATE_CY} r={GATE_R - 3} />
1162
+ </clipPath>
1163
+ <linearGradient id="sweep-grad" x1="0" x2="1" y1="0" y2="0">
1164
+ <stop offset="0%" stopColor="rgba(165,180,252,0)" />
1165
+ <stop offset="55%" stopColor="rgba(165,180,252,0.18)" />
1166
+ <stop offset="100%" stopColor="rgba(199,210,254,0.76)" />
1167
+ </linearGradient>
1168
+
1169
+ {/* Reusable path definitions referenced by mpath */}
1170
+ {scenario.agents.map((agent, i) => (
1171
+ <path
1172
+ key={`def-a-${scenario.id}-${agent.id}`}
1173
+ id={`path-a2g-${scenario.id}-${agent.id}`}
1174
+ d={pathAgentToGate(i)}
1175
+ />
1176
+ ))}
1177
+ <path id={`path-g2r-${scenario.id}-primary`} d={pathGateToResource('primary')} />
1178
+ <path id={`path-g2r-${scenario.id}-secondary`} d={pathGateToResource('secondary')} />
1179
+ </defs>
1180
+
1181
+ <BackgroundGrid />
1182
+
1183
+ {/* Connector lines (agent → gate) */}
1184
+ {scenario.agents.map((agent, i) => (
1185
+ <ConnectorPath
1186
+ key={`conn-a-${agent.id}`}
1187
+ d={pathAgentToGate(i)}
1188
+ highlighted={phase === 'request' || phase === 'decide' || phase === 'route'}
1189
+ color={agent.hue}
1190
+ />
1191
+ ))}
1192
+
1193
+ {/* Connector lines (gate → resources) */}
1194
+ <ConnectorPath
1195
+ d={pathGateToResource('primary')}
1196
+ highlighted={phase === 'route' || phase === 'settled'}
1197
+ color={primaryClaimer?.hue ?? '#34d399'}
1198
+ />
1199
+ <ConnectorPath
1200
+ d={pathGateToResource('secondary')}
1201
+ highlighted={phase === 'route' || phase === 'settled'}
1202
+ color={secondaryClaimer?.hue ?? '#34d399'}
1203
+ />
1204
+
1205
+ {/* Request packets: agent → gate */}
1206
+ {scenario.agents.map((agent) => (
1207
+ <Packet
1208
+ key={`req-${agent.id}`}
1209
+ pathId={`path-a2g-${scenario.id}-${agent.id}`}
1210
+ start={0.12}
1211
+ end={0.39}
1212
+ visibleUntil={0.52}
1213
+ color={agent.hue}
1214
+ size={5}
1215
+ />
1216
+ ))}
1217
+
1218
+ {/* Route packets: proceed agents → resources */}
1219
+ {scenario.agents
1220
+ .filter((a) => a.outcome === 'proceed')
1221
+ .map((agent) => (
1222
+ <Packet
1223
+ key={`route-fwd-${agent.id}`}
1224
+ pathId={`path-g2r-${scenario.id}-${agent.resource}`}
1225
+ start={0.5}
1226
+ end={0.73}
1227
+ color="#34d399"
1228
+ size={6}
1229
+ />
1230
+ ))}
1231
+
1232
+ {/* Route packets: skip_run agents (bounce back gate → agent in red) */}
1233
+ {scenario.agents
1234
+ .filter((a) => a.outcome === 'skip_run')
1235
+ .map((agent) => (
1236
+ <Packet
1237
+ key={`route-back-${agent.id}`}
1238
+ pathId={`path-a2g-${scenario.id}-${agent.id}`}
1239
+ start={0.5}
1240
+ end={0.73}
1241
+ color="#f87171"
1242
+ size={5}
1243
+ reverse
1244
+ />
1245
+ ))}
1246
+
1247
+ {/* Gate */}
1248
+ <GateNode phase={phase} scenario={scenario} />
1249
+
1250
+ {/* Agent cards */}
1251
+ {scenario.agents.map((agent, i) => (
1252
+ <AgentCard key={agent.id} agent={agent} index={i} phase={phase} />
1253
+ ))}
1254
+
1255
+ {/* Resource cards */}
1256
+ <ResourceCard
1257
+ resource="primary"
1258
+ phase={phase}
1259
+ claimer={primaryClaimer}
1260
+ copy={scenario.resources.primary}
1261
+ />
1262
+ <ResourceCard
1263
+ resource="secondary"
1264
+ phase={phase}
1265
+ claimer={secondaryClaimer}
1266
+ copy={scenario.resources.secondary}
1267
+ />
1268
+
1269
+ <ScenarioFlowLayer
1270
+ scenario={scenario}
1271
+ phase={phase}
1272
+ primaryClaimer={primaryClaimer}
1273
+ secondaryClaimer={secondaryClaimer}
1274
+ />
1275
+ </svg>
1276
+ );
1277
+ }
1278
+
1279
+ // --- Live phase ticker (under the stage) ---
1280
+ function PhaseTicker({ phase }: { phase: Phase }) {
1281
+ const steps: Array<{ id: Phase; label: string }> = [
1282
+ { id: 'request', label: 'Agents check in' },
1283
+ { id: 'decide', label: 'Gate evaluates' },
1284
+ { id: 'route', label: 'Decisions routed' },
1285
+ { id: 'settled', label: 'Resources protected' },
1286
+ ];
1287
+ const activeIdx = steps.findIndex((s) => s.id === phase);
1288
+
1289
+ return (
1290
+ <div className="mt-4 flex flex-wrap items-center gap-2 rounded border border-border bg-bg/60 px-3 py-2">
1291
+ {steps.map((step, i) => {
1292
+ const reached = activeIdx >= i;
1293
+ const current = activeIdx === i;
1294
+ return (
1295
+ <div className="flex items-center gap-2" key={step.id}>
1296
+ <div className="flex items-center gap-2">
1297
+ <span
1298
+ className={`flex h-4 w-4 items-center justify-center rounded-full border text-[9px] font-mono transition-colors duration-500 ${
1299
+ current
1300
+ ? 'border-accent bg-accent/30 text-accent'
1301
+ : reached
1302
+ ? 'border-success/60 bg-success/20 text-success'
1303
+ : 'border-border bg-bg text-text-tertiary'
1304
+ }`}
1305
+ >
1306
+ {reached && !current ? <Check className="h-2.5 w-2.5" /> : i + 1}
1307
+ </span>
1308
+ <span
1309
+ className={`text-[11px] transition-colors duration-500 ${
1310
+ current
1311
+ ? 'text-text-primary'
1312
+ : reached
1313
+ ? 'text-text-secondary'
1314
+ : 'text-text-tertiary'
1315
+ }`}
1316
+ >
1317
+ {step.label}
1318
+ </span>
1319
+ </div>
1320
+ {i < steps.length - 1 && (
1321
+ <div className="h-px w-6 bg-border" aria-hidden="true" />
1322
+ )}
1323
+ </div>
1324
+ );
1325
+ })}
1326
+ </div>
1327
+ );
1328
+ }
1329
+
1330
+ function ScenarioPreview({
1331
+ scenario,
1332
+ active,
1333
+ }: {
1334
+ scenario: Scenario;
1335
+ active: boolean;
1336
+ }) {
1337
+ if (scenario.id === 'reservations') {
1338
+ return (
1339
+ <span
1340
+ className={`grid h-14 w-24 shrink-0 grid-rows-[auto_1fr] rounded border px-2 py-1.5 transition-colors duration-300 ${
1341
+ active
1342
+ ? 'border-warning/45 bg-warning/10'
1343
+ : 'border-border bg-surface group-hover:border-warning/25'
1344
+ }`}
1345
+ aria-hidden="true"
1346
+ >
1347
+ <span className="flex items-center justify-between">
1348
+ <Clock3 className={`h-3 w-3 ${active ? 'text-warning' : 'text-text-tertiary'}`} />
1349
+ <span className="font-mono text-[8px] text-text-tertiary">10:00</span>
1350
+ </span>
1351
+ <span className="mt-1 grid grid-cols-4 items-end gap-1">
1352
+ {[0, 1, 2, 3].map((item) => (
1353
+ <span
1354
+ className={`rounded-sm transition-all duration-500 ${
1355
+ item === 1
1356
+ ? active
1357
+ ? 'h-6 bg-warning shadow-[0_0_12px_rgba(251,146,60,0.45)]'
1358
+ : 'h-5 bg-warning/45'
1359
+ : item === 2
1360
+ ? 'h-3 bg-error/45'
1361
+ : 'h-2 bg-border'
1362
+ }`}
1363
+ key={item}
1364
+ />
1365
+ ))}
1366
+ </span>
1367
+ </span>
1368
+ );
1369
+ }
1370
+
1371
+ if (scenario.id === 'history') {
1372
+ return (
1373
+ <span
1374
+ className={`grid h-14 w-24 shrink-0 gap-1 rounded border px-2 py-1.5 transition-colors duration-300 ${
1375
+ active
1376
+ ? 'border-success/45 bg-success/10'
1377
+ : 'border-border bg-surface group-hover:border-success/25'
1378
+ }`}
1379
+ aria-hidden="true"
1380
+ >
1381
+ {['check', 'claim', 'skip'].map((event, index) => (
1382
+ <span className="flex items-center gap-1.5" key={event}>
1383
+ <span
1384
+ className={`h-1.5 w-1.5 rounded-full ${
1385
+ index === 2 ? 'bg-error' : active ? 'bg-success' : 'bg-text-tertiary'
1386
+ } ${active && index !== 2 ? 'animate-pulse' : ''}`}
1387
+ />
1388
+ <span
1389
+ className={`h-1.5 rounded-full ${
1390
+ active ? 'bg-success/35' : 'bg-border'
1391
+ }`}
1392
+ style={{ width: `${34 + index * 12}px` }}
1393
+ />
1394
+ </span>
1395
+ ))}
1396
+ </span>
1397
+ );
1398
+ }
1399
+
1400
+ return (
1401
+ <span
1402
+ className={`grid h-14 w-24 shrink-0 place-items-center rounded border transition-colors duration-300 ${
1403
+ active
1404
+ ? 'border-accent/45 bg-accent/10'
1405
+ : 'border-border bg-surface group-hover:border-accent/25'
1406
+ }`}
1407
+ aria-hidden="true"
1408
+ >
1409
+ <span className="flex items-center gap-1.5 font-mono text-[9px]">
1410
+ <span className={active ? 'text-warning' : 'text-text-tertiary'}>20</span>
1411
+ <span className="text-text-tertiary">&gt;</span>
1412
+ <span className={active ? 'text-accent' : 'text-text-tertiary'}>10</span>
1413
+ <span className="text-text-tertiary">&gt;</span>
1414
+ <span className={active ? 'text-success' : 'text-text-tertiary'}>0</span>
1415
+ </span>
1416
+ <Zap className={`h-3.5 w-3.5 ${active ? 'text-warning' : 'text-text-tertiary'}`} />
1417
+ </span>
1418
+ );
1419
+ }
1420
+
1421
+ export function AgentCoordinationStory() {
1422
+ const [reduced, setReduced] = useState(false);
1423
+ const [selectedScenarioId, setSelectedScenarioId] = useState<ScenarioId>('priority');
1424
+
1425
+ useEffect(() => {
1426
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
1427
+ setReduced(mq.matches);
1428
+ const handler = () => setReduced(mq.matches);
1429
+ mq.addEventListener('change', handler);
1430
+ return () => mq.removeEventListener('change', handler);
1431
+ }, []);
1432
+
1433
+ const { phase } = useScenario(reduced);
1434
+ const settled = phase === 'settled';
1435
+ const selectedScenario =
1436
+ scenarios.find((scenario) => scenario.id === selectedScenarioId) ?? defaultScenario;
1437
+
1438
+ return (
1439
+ <section className="border-b border-border bg-surface">
1440
+ <div className="mx-auto grid max-w-7xl gap-10 px-4 py-20 lg:grid-cols-[0.55fr_1.45fr]">
1441
+ <div>
1442
+ <p className="text-label uppercase text-text-tertiary">Agent traffic control</p>
1443
+ <h2 className="mt-3 max-w-xl text-3xl font-semibold leading-tight text-text-primary">
1444
+ {selectedScenario.heading}
1445
+ </h2>
1446
+ <p className="mt-5 max-w-xl text-heading leading-7 text-text-secondary">
1447
+ {selectedScenario.intro}
1448
+ </p>
1449
+ <p className="mt-4 max-w-xl text-body leading-6 text-text-secondary">
1450
+ {selectedScenario.detail}
1451
+ </p>
1452
+ <div className="mt-8 grid gap-3">
1453
+ {scenarios.map((scenario) => (
1454
+ <button
1455
+ type="button"
1456
+ className={`group relative overflow-hidden rounded border px-4 py-3 text-left transition-all duration-300 ${
1457
+ selectedScenario.id === scenario.id
1458
+ ? 'border-accent/60 bg-accent/10 shadow-[0_0_28px_rgba(94,106,210,0.16)]'
1459
+ : 'border-border bg-bg hover:border-accent/30 hover:bg-surface-raised'
1460
+ }`}
1461
+ key={scenario.id}
1462
+ onClick={() => setSelectedScenarioId(scenario.id)}
1463
+ aria-pressed={selectedScenario.id === scenario.id}
1464
+ >
1465
+ <span
1466
+ className={`absolute left-0 top-0 h-full w-0.5 transition-colors duration-300 ${
1467
+ selectedScenario.id === scenario.id ? 'bg-accent' : 'bg-transparent'
1468
+ }`}
1469
+ aria-hidden="true"
1470
+ />
1471
+ <span className="flex items-start justify-between gap-4">
1472
+ <span className="min-w-0">
1473
+ <span className="flex items-center gap-2 text-body font-medium text-text-primary">
1474
+ {scenario.id === 'history' && <ListChecks className="h-3.5 w-3.5 text-success" />}
1475
+ {scenario.id === 'reservations' && <Clock3 className="h-3.5 w-3.5 text-warning" />}
1476
+ {scenario.id === 'priority' && <Zap className="h-3.5 w-3.5 text-accent" />}
1477
+ {scenario.title}
1478
+ </span>
1479
+ <span className="mt-1 block text-body leading-6 text-text-secondary">
1480
+ {scenario.body}
1481
+ </span>
1482
+ </span>
1483
+ <ScenarioPreview scenario={scenario} active={selectedScenario.id === scenario.id} />
1484
+ </span>
1485
+ </button>
1486
+ ))}
1487
+ </div>
1488
+ </div>
1489
+
1490
+ <div className="relative overflow-hidden rounded border border-border bg-[linear-gradient(135deg,rgba(20,20,28,0.98),rgba(14,16,24,0.98))] p-4 shadow-[0_28px_90px_rgba(0,0,0,0.38)]">
1491
+ <div className="absolute inset-0 bg-[radial-gradient(circle_at_50%_8%,rgba(94,106,210,0.22),transparent_38%),radial-gradient(circle_at_85%_82%,rgba(42,179,123,0.12),transparent_30%)]" />
1492
+ <div className="relative">
1493
+ <div className="mb-3 flex flex-wrap items-center justify-between gap-3 rounded border border-accent/30 bg-accent/10 px-4 py-3">
1494
+ <div className="flex items-center gap-3">
1495
+ <div className="flex h-10 w-10 items-center justify-center rounded border border-accent/40 bg-bg text-accent">
1496
+ <RadioTower className="h-4 w-4" />
1497
+ </div>
1498
+ <div>
1499
+ <p className="text-label uppercase text-text-tertiary">
1500
+ {selectedScenario.eyebrow}
1501
+ </p>
1502
+ <p className="text-body font-medium text-text-primary">
1503
+ {settled
1504
+ ? selectedScenario.settled
1505
+ : selectedScenario.live}
1506
+ </p>
1507
+ </div>
1508
+ </div>
1509
+ <div className="flex items-center gap-2 rounded bg-bg px-3 py-2">
1510
+ {settled ? (
1511
+ <Check className="h-4 w-4 text-success" />
1512
+ ) : (
1513
+ <Zap className="h-4 w-4 text-warning" />
1514
+ )}
1515
+ <span
1516
+ className={`font-mono text-[11px] ${settled ? 'text-success' : 'text-warning'}`}
1517
+ >
1518
+ {settled ? 'protected' : phase}
1519
+ </span>
1520
+ </div>
1521
+ </div>
1522
+
1523
+ <CoordinationStage phase={phase} scenario={selectedScenario} />
1524
+ <PhaseTicker phase={phase} />
1525
+ </div>
1526
+ </div>
1527
+ </div>
1528
+ </section>
1529
+ );
1530
+ }