ui-syncup 0.3.13 → 0.4.0-beta.2

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 (1047) hide show
  1. package/.agents/skills/ai-spec-workflow/SKILL.md +58 -0
  2. package/.agents/skills/ai-spec-workflow/references/AI_SPECIFICATION_WORKFLOW.md +1434 -0
  3. package/.agents/skills/ai-spec-workflow/references/templates/design-template.md +729 -0
  4. package/.agents/skills/ai-spec-workflow/references/templates/requirements-template.md +179 -0
  5. package/.agents/skills/ai-spec-workflow/references/templates/tasks-template.md +501 -0
  6. package/.agents/skills/animation-designer/SKILL.md +688 -0
  7. package/.agents/skills/animation-designer/manifest.yaml +44 -0
  8. package/.agents/skills/brainstorming/SKILL.md +54 -0
  9. package/.agents/skills/contract-driven-ui/SKILL.md +270 -0
  10. package/.agents/skills/dispatching-parallel-agents/SKILL.md +180 -0
  11. package/.agents/skills/executing-plans/SKILL.md +76 -0
  12. package/.agents/skills/executing-specs/SKILL.md +53 -0
  13. package/.agents/skills/finishing-a-development-branch/SKILL.md +200 -0
  14. package/.agents/skills/github-workflow-automation/SKILL.md +846 -0
  15. package/.agents/skills/react-best-practices/AGENTS.md +2249 -0
  16. package/.agents/skills/react-best-practices/README.md +123 -0
  17. package/.agents/skills/react-best-practices/SKILL.md +121 -0
  18. package/.agents/skills/react-best-practices/metadata.json +15 -0
  19. package/.agents/skills/react-best-practices/rules/_sections.md +46 -0
  20. package/.agents/skills/react-best-practices/rules/_template.md +28 -0
  21. package/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  22. package/.agents/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
  23. package/.agents/skills/react-best-practices/rules/async-api-routes.md +38 -0
  24. package/.agents/skills/react-best-practices/rules/async-defer-await.md +80 -0
  25. package/.agents/skills/react-best-practices/rules/async-dependencies.md +36 -0
  26. package/.agents/skills/react-best-practices/rules/async-parallel.md +28 -0
  27. package/.agents/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
  28. package/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
  29. package/.agents/skills/react-best-practices/rules/bundle-conditional.md +31 -0
  30. package/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
  31. package/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  32. package/.agents/skills/react-best-practices/rules/bundle-preload.md +50 -0
  33. package/.agents/skills/react-best-practices/rules/client-event-listeners.md +74 -0
  34. package/.agents/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
  35. package/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
  36. package/.agents/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
  37. package/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
  38. package/.agents/skills/react-best-practices/rules/js-cache-storage.md +70 -0
  39. package/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
  40. package/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
  41. package/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
  42. package/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
  43. package/.agents/skills/react-best-practices/rules/js-length-check-first.md +49 -0
  44. package/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
  45. package/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
  46. package/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
  47. package/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
  48. package/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  49. package/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
  50. package/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
  51. package/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  52. package/.agents/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  53. package/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
  54. package/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
  55. package/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
  56. package/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
  57. package/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
  58. package/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  59. package/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
  60. package/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
  61. package/.agents/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
  62. package/.agents/skills/react-best-practices/rules/server-cache-lru.md +41 -0
  63. package/.agents/skills/react-best-practices/rules/server-cache-react.md +26 -0
  64. package/.agents/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
  65. package/.agents/skills/react-best-practices/rules/server-serialization.md +38 -0
  66. package/.agents/skills/react-ui-patterns/SKILL.md +289 -0
  67. package/.agents/skills/receiving-code-review/SKILL.md +213 -0
  68. package/.agents/skills/requesting-code-review/SKILL.md +105 -0
  69. package/.agents/skills/requesting-code-review/code-reviewer.md +146 -0
  70. package/.agents/skills/reviewing-code/SKILL.md +28 -0
  71. package/.agents/skills/shadcn/SKILL.md +240 -0
  72. package/.agents/skills/shadcn/agents/openai.yml +5 -0
  73. package/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  74. package/.agents/skills/shadcn/assets/shadcn.png +0 -0
  75. package/.agents/skills/shadcn/cli.md +255 -0
  76. package/.agents/skills/shadcn/customization.md +202 -0
  77. package/.agents/skills/shadcn/evals/evals.json +47 -0
  78. package/.agents/skills/shadcn/mcp.md +94 -0
  79. package/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  80. package/.agents/skills/shadcn/rules/composition.md +195 -0
  81. package/.agents/skills/shadcn/rules/forms.md +192 -0
  82. package/.agents/skills/shadcn/rules/icons.md +101 -0
  83. package/.agents/skills/shadcn/rules/styling.md +162 -0
  84. package/.agents/skills/steering-creation/SKILL.md +221 -0
  85. package/.agents/skills/steering-creation/references/STEERING_CREATION_INSTRUCTION.md +850 -0
  86. package/.agents/skills/subagent-driven-development/SKILL.md +240 -0
  87. package/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
  88. package/.agents/skills/subagent-driven-development/implementer-prompt.md +78 -0
  89. package/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  90. package/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -0
  91. package/.agents/skills/systematic-debugging/SKILL.md +296 -0
  92. package/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  93. package/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -0
  94. package/.agents/skills/systematic-debugging/defense-in-depth.md +122 -0
  95. package/.agents/skills/systematic-debugging/find-polluter.sh +63 -0
  96. package/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -0
  97. package/.agents/skills/systematic-debugging/test-academic.md +14 -0
  98. package/.agents/skills/systematic-debugging/test-pressure-1.md +58 -0
  99. package/.agents/skills/systematic-debugging/test-pressure-2.md +68 -0
  100. package/.agents/skills/systematic-debugging/test-pressure-3.md +69 -0
  101. package/.agents/skills/test-driven-development/SKILL.md +371 -0
  102. package/.agents/skills/test-driven-development/testing-anti-patterns.md +299 -0
  103. package/.agents/skills/using-git-worktrees/SKILL.md +217 -0
  104. package/.agents/skills/using-superpowers/SKILL.md +87 -0
  105. package/.agents/skills/verification-before-completion/SKILL.md +139 -0
  106. package/.agents/skills/web-design-guidelines/SKILL.md +36 -0
  107. package/.agents/skills/writing-plans/SKILL.md +116 -0
  108. package/.agents/skills/writing-skills/SKILL.md +655 -0
  109. package/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
  110. package/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  111. package/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
  112. package/.agents/skills/writing-skills/persuasion-principles.md +187 -0
  113. package/.agents/skills/writing-skills/render-graphs.js +168 -0
  114. package/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  115. package/.ai/steering/product.md +51 -0
  116. package/.ai/steering/structure.md +275 -0
  117. package/.ai/steering/tech.md +188 -0
  118. package/.claude/agents/database-architect.md +96 -0
  119. package/.claude/agents/deployment-pipeline-architect.md +122 -0
  120. package/.claude/agents/nextjs-expert.md +69 -0
  121. package/.claude/agents/ui-design-expert.md +106 -0
  122. package/.claudeignore +69 -0
  123. package/.dockerignore +8 -0
  124. package/.env.development +86 -0
  125. package/.env.example +171 -0
  126. package/.env.production +139 -0
  127. package/.env.test +58 -0
  128. package/.gitattributes +2 -0
  129. package/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
  130. package/.github/ISSUE_TEMPLATE/feature_request.yml +20 -0
  131. package/.github/PULL_REQUEST_TEMPLATE.md +23 -0
  132. package/.github/workflows/ci.yml +64 -0
  133. package/.github/workflows/release.yml +174 -0
  134. package/.nvmrc +1 -0
  135. package/.releaserc.json +18 -0
  136. package/.vercelignore +73 -0
  137. package/AGENTS.md +544 -0
  138. package/CHANGELOG.md +69 -0
  139. package/CODE_OF_CONDUCT.md +21 -0
  140. package/CONTRIBUTING.md +32 -0
  141. package/Dockerfile +84 -0
  142. package/LICENSE +21 -0
  143. package/README.md +328 -59
  144. package/SECURITY.md +16 -0
  145. package/bun.lock +3853 -0
  146. package/cli/README.md +94 -0
  147. package/cli/bun.lock +306 -0
  148. package/cli/index.ts +96 -0
  149. package/cli/package-lock.json +2157 -0
  150. package/cli/package.json +30 -0
  151. package/cli/src/commands/backup.ts +78 -0
  152. package/cli/src/commands/doctor.ts +82 -0
  153. package/cli/src/commands/init.ts +234 -0
  154. package/cli/src/commands/logs.ts +26 -0
  155. package/cli/src/commands/open.ts +23 -0
  156. package/cli/src/commands/remove.ts +44 -0
  157. package/cli/src/commands/restart.ts +21 -0
  158. package/cli/src/commands/restore.ts +90 -0
  159. package/cli/src/commands/start.ts +26 -0
  160. package/cli/src/commands/status.ts +25 -0
  161. package/cli/src/commands/stop.ts +20 -0
  162. package/cli/src/commands/upgrade.ts +28 -0
  163. package/cli/src/lib/docker.ts +40 -0
  164. package/cli/src/lib/env.ts +42 -0
  165. package/cli/src/lib/ui.ts +43 -0
  166. package/cli/tsconfig.json +13 -0
  167. package/cli/tsup.config.ts +12 -0
  168. package/components.json +24 -0
  169. package/docker/README.md +430 -0
  170. package/docker/compose.dev-minio.yml +30 -0
  171. package/docker/compose.dev.yml +39 -0
  172. package/docker/compose.local.yml +84 -0
  173. package/docker/compose.yml +153 -0
  174. package/docs/VERSIONING.md +101 -0
  175. package/docs/database/DRIZZLE_COMMANDS_EXPLAINED.md +1779 -0
  176. package/docs/database/DRIZZLE_ZOD_POSTGRESQL_INSTRUCTION.md +646 -0
  177. package/docs/database/MIGRATION_BEST_PRACTICES.md +601 -0
  178. package/docs/database/MIGRATION_ROLLBACK.md +1080 -0
  179. package/docs/database/MIGRATION_SYSTEM.md +165 -0
  180. package/docs/database/MIGRATION_TROUBLESHOOTING.md +881 -0
  181. package/docs/development/ENVIRONMENT_CONFIG.md +896 -0
  182. package/docs/development/LOCAL_DEVELOPMENT.md +456 -0
  183. package/docs/development/REMOTE_DATABASE_SETUP.md +786 -0
  184. package/docs/development/STORAGE_SETUP.md +207 -0
  185. package/docs/development/SUPABASE_LOCAL_SETUP.md +178 -0
  186. package/docs/development/TESTING.md +714 -0
  187. package/docs/feature-architectures/LOADING_ARCHITECTURE.md +343 -0
  188. package/docs/feature-architectures/NOTIFICATION_ARCHITECTURE.md +858 -0
  189. package/docs/feature-architectures/RATE_LIMIT_RESET.md +147 -0
  190. package/docs/feature-architectures/RBAC.md +1132 -0
  191. package/docs/feature-architectures/RESOURCE_LIMITS.md +69 -0
  192. package/docs/feature-architectures/SECURITY.md +284 -0
  193. package/docs/feature-architectures/WORKSPACES.md +278 -0
  194. package/docs/plans/admin-setup-wizard-routing-plan.md +623 -0
  195. package/drizzle/0000_purple_wilson_fisk.sql +360 -0
  196. package/drizzle/0001_drop_instance_public_url.sql +1 -0
  197. package/drizzle/meta/0000_snapshot.json +3118 -0
  198. package/drizzle/meta/_journal.json +20 -0
  199. package/drizzle.config.ts +13 -0
  200. package/eslint.config.mjs +44 -0
  201. package/install.sh +180 -0
  202. package/next.config.ts +91 -0
  203. package/package.json +128 -22
  204. package/playwright.config.ts +70 -0
  205. package/postcss.config.mjs +7 -0
  206. package/public/file.svg +1 -0
  207. package/public/globe.svg +1 -0
  208. package/public/logo.svg +11 -0
  209. package/public/next.svg +1 -0
  210. package/public/playground/CPM-101/as-is-image.jpg +0 -0
  211. package/public/playground/CPM-101/to-be-image.jpg +0 -0
  212. package/public/playground/TEST-1/LinkedIn-skeleton-screen.png +0 -0
  213. package/public/playground/TEST-1/https___dev-to-uploads.s3.amazonaws.com_uploads_articles_vuahe90ka1mkx9aepmea.webp +0 -0
  214. package/public/playground/TEST-1/linkedin_skeletonscreen.jpg +0 -0
  215. package/public/vercel.svg +1 -0
  216. package/public/window.svg +1 -0
  217. package/scripts/__tests__/migrate.integration.test.ts +642 -0
  218. package/scripts/__tests__/migrate.property.test.ts +1714 -0
  219. package/scripts/__tests__/migrate.test.ts +536 -0
  220. package/scripts/admin-reset-password.ts +114 -0
  221. package/scripts/check-email-queue.ts +99 -0
  222. package/scripts/check-sessions.ts +50 -0
  223. package/scripts/db-pull-data.sh +73 -0
  224. package/scripts/force-verify-email.sh +13 -0
  225. package/scripts/migrate.ts +693 -0
  226. package/scripts/process-email-queue.ts +26 -0
  227. package/scripts/reset-db.ts +47 -0
  228. package/scripts/reset-rate-limit.sh +26 -0
  229. package/scripts/reset-remote-db.sql +31 -0
  230. package/scripts/retry-failed-emails.ts +67 -0
  231. package/scripts/seed.ts +605 -0
  232. package/scripts/setup-monitoring.sh +440 -0
  233. package/scripts/sync-migration-tracking.ts +113 -0
  234. package/scripts/test-ci-error-handling.sh +237 -0
  235. package/scripts/test-ci-workflow.sh +200 -0
  236. package/scripts/test-migration.sh +151 -0
  237. package/scripts/validate-env.ts +25 -0
  238. package/scripts/validate-migration-system.ts +566 -0
  239. package/scripts/verify-ci-status-reporting.sh +206 -0
  240. package/scripts/verify-user-email.sql +22 -0
  241. package/scripts/verify-vercel-integration.ts +292 -0
  242. package/seed_data.md +54 -0
  243. package/src/app/(protected)/(team)/(routes)/[projectSlug]/error.tsx +89 -0
  244. package/src/app/(protected)/(team)/(routes)/[projectSlug]/loading.tsx +101 -0
  245. package/src/app/(protected)/(team)/(routes)/[projectSlug]/page.tsx +91 -0
  246. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/README.md +192 -0
  247. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/error.tsx +58 -0
  248. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/loading.tsx +14 -0
  249. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/not-found.tsx +47 -0
  250. package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/page.tsx +91 -0
  251. package/src/app/(protected)/(team)/projects/page.tsx +16 -0
  252. package/src/app/(protected)/(team)/team/settings/(section)/instance/page.tsx +52 -0
  253. package/src/app/(protected)/(team)/team/settings/(section)/integrations/loading.tsx +5 -0
  254. package/src/app/(protected)/(team)/team/settings/(section)/integrations/page.tsx +23 -0
  255. package/src/app/(protected)/(team)/team/settings/(section)/members/loading.tsx +5 -0
  256. package/src/app/(protected)/(team)/team/settings/(section)/members/page.tsx +35 -0
  257. package/src/app/(protected)/(team)/team/settings/layout.tsx +72 -0
  258. package/src/app/(protected)/(team)/team/settings/loading.tsx +5 -0
  259. package/src/app/(protected)/(team)/team/settings/page.tsx +71 -0
  260. package/src/app/(protected)/dev/auth/README.md +151 -0
  261. package/src/app/(protected)/dev/auth/page.tsx +590 -0
  262. package/src/app/(protected)/layout.test.tsx +209 -0
  263. package/src/app/(protected)/layout.tsx +28 -0
  264. package/src/app/(protected)/onboarding/page.tsx +27 -0
  265. package/src/app/(protected)/settings/integrations/page.tsx +23 -0
  266. package/src/app/(protected)/settings/layout.tsx +26 -0
  267. package/src/app/(protected)/settings/notifications/page.tsx +26 -0
  268. package/src/app/(protected)/settings/other/page.tsx +23 -0
  269. package/src/app/(protected)/settings/page.tsx +23 -0
  270. package/src/app/(protected)/settings/preferences/page.tsx +23 -0
  271. package/src/app/(protected)/settings/security/page.tsx +37 -0
  272. package/src/app/(public)/forgot-password/page.tsx +20 -0
  273. package/src/app/(public)/invite/project/[token]/error.tsx +50 -0
  274. package/src/app/(public)/invite/project/[token]/loading.tsx +39 -0
  275. package/src/app/(public)/invite/project/[token]/page.tsx +156 -0
  276. package/src/app/(public)/layout.tsx +9 -0
  277. package/src/app/(public)/privacy-policy/page.tsx +12 -0
  278. package/src/app/(public)/reset-password/page.tsx +37 -0
  279. package/src/app/(public)/setup/__tests__/page.test.tsx +30 -0
  280. package/src/app/(public)/setup/page.tsx +17 -0
  281. package/src/app/(public)/share/issue/[token]/page.tsx +51 -0
  282. package/src/app/(public)/sign-in/page.tsx +55 -0
  283. package/src/app/(public)/sign-up/page.tsx +23 -0
  284. package/src/app/(public)/verify-email/page.tsx +22 -0
  285. package/src/app/(public)/verify-email-confirm/page.tsx +40 -0
  286. package/src/app/api/auth/[...all]/route.ts +6 -0
  287. package/src/app/api/auth/delete-account/route.ts +134 -0
  288. package/src/app/api/auth/dev/force-verify/route.ts +180 -0
  289. package/src/app/api/auth/dev/reset-rate-limit/route.ts +144 -0
  290. package/src/app/api/auth/dev/sessions/route.ts +172 -0
  291. package/src/app/api/auth/forgot-password/__tests__/forgot-password.property.test.ts +397 -0
  292. package/src/app/api/auth/forgot-password/route.ts +277 -0
  293. package/src/app/api/auth/logout/route.ts +115 -0
  294. package/src/app/api/auth/me/route.ts +123 -0
  295. package/src/app/api/auth/providers/__tests__/route.test.ts +236 -0
  296. package/src/app/api/auth/providers/route.ts +119 -0
  297. package/src/app/api/auth/resend-verification/route.ts +262 -0
  298. package/src/app/api/auth/reset-password/__tests__/reset-password.property.test.ts +493 -0
  299. package/src/app/api/auth/reset-password/__tests__/route.test.ts +284 -0
  300. package/src/app/api/auth/reset-password/route.ts +251 -0
  301. package/src/app/api/auth/verify-email/route.ts +232 -0
  302. package/src/app/api/example-cors/route.ts +61 -0
  303. package/src/app/api/health/route.ts +14 -0
  304. package/src/app/api/invite/project/[token]/__tests__/accept-invitation.integration.test.ts +348 -0
  305. package/src/app/api/invite/project/[token]/decline/route.ts +99 -0
  306. package/src/app/api/invite/project/[token]/route.ts +269 -0
  307. package/src/app/api/issues/[issueId]/activities/route.ts +213 -0
  308. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/[commentId]/route.ts +486 -0
  309. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/route.ts +283 -0
  310. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/read/route.ts +242 -0
  311. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/route.ts +534 -0
  312. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/route.ts +514 -0
  313. package/src/app/api/issues/[issueId]/attachments/[attachmentId]/route.ts +161 -0
  314. package/src/app/api/issues/[issueId]/attachments/route.ts +376 -0
  315. package/src/app/api/issues/[issueId]/route.ts +516 -0
  316. package/src/app/api/notifications/[id]/read/route.ts +131 -0
  317. package/src/app/api/notifications/__tests__/notifications.integration.test.ts +350 -0
  318. package/src/app/api/notifications/read-all/route.ts +72 -0
  319. package/src/app/api/notifications/route.ts +148 -0
  320. package/src/app/api/notifications/unread-count/route.ts +77 -0
  321. package/src/app/api/projects/[id]/activities/route.ts +174 -0
  322. package/src/app/api/projects/[id]/invitations/[invitationId]/resend/route.ts +99 -0
  323. package/src/app/api/projects/[id]/invitations/[invitationId]/route.ts +96 -0
  324. package/src/app/api/projects/[id]/invitations/route.ts +254 -0
  325. package/src/app/api/projects/[id]/issues/route.ts +452 -0
  326. package/src/app/api/projects/[id]/join/route.ts +207 -0
  327. package/src/app/api/projects/[id]/members/[memberId]/route.ts +364 -0
  328. package/src/app/api/projects/[id]/members/me/route.ts +121 -0
  329. package/src/app/api/projects/[id]/members/route.ts +129 -0
  330. package/src/app/api/projects/[id]/route.ts +476 -0
  331. package/src/app/api/projects/route.ts +394 -0
  332. package/src/app/api/setup/admin/route.ts +255 -0
  333. package/src/app/api/setup/complete/__tests__/route.test.ts +60 -0
  334. package/src/app/api/setup/complete/route.ts +244 -0
  335. package/src/app/api/setup/config/route.ts +195 -0
  336. package/src/app/api/setup/export/route.ts +111 -0
  337. package/src/app/api/setup/health/route.ts +74 -0
  338. package/src/app/api/setup/import/route.ts +154 -0
  339. package/src/app/api/setup/status/route.ts +82 -0
  340. package/src/app/api/setup/workspace/route.ts +252 -0
  341. package/src/app/api/teams/[teamId]/export/route.ts +115 -0
  342. package/src/app/api/teams/[teamId]/invitations/[invitationId]/resend/route.ts +132 -0
  343. package/src/app/api/teams/[teamId]/invitations/[invitationId]/route.ts +117 -0
  344. package/src/app/api/teams/[teamId]/invitations/route.ts +363 -0
  345. package/src/app/api/teams/[teamId]/members/[userId]/route.ts +335 -0
  346. package/src/app/api/teams/[teamId]/members/route.ts +184 -0
  347. package/src/app/api/teams/[teamId]/members/search/route.ts +202 -0
  348. package/src/app/api/teams/[teamId]/route.ts +423 -0
  349. package/src/app/api/teams/[teamId]/switch/route.ts +140 -0
  350. package/src/app/api/teams/[teamId]/transfer-ownership/route.ts +212 -0
  351. package/src/app/api/teams/invitations/[token]/accept/route.ts +140 -0
  352. package/src/app/api/teams/invitations/by-id/[id]/accept/route.ts +98 -0
  353. package/src/app/api/teams/invitations/by-id/[id]/decline/route.ts +90 -0
  354. package/src/app/api/teams/route.ts +278 -0
  355. package/src/app/api/uploads/media/route.ts +118 -0
  356. package/src/app/api/uploads/presigned/route.ts +49 -0
  357. package/src/app/api/user/linked-accounts/route.ts +35 -0
  358. package/src/app/email-preview/page.tsx +11 -0
  359. package/src/app/favicon.ico +0 -0
  360. package/src/app/global-error.tsx +21 -0
  361. package/src/app/layout.tsx +50 -0
  362. package/src/app/page.tsx +5 -0
  363. package/src/components/icons/atlassian-icon.tsx +22 -0
  364. package/src/components/icons/index.ts +1 -0
  365. package/src/components/layout/SIDEBAR_LAYOUT_BEST_PRACTICES.md +240 -0
  366. package/src/components/layout/app-shell-header-store.tsx +20 -0
  367. package/src/components/layout/app-shell-skeleton.tsx +89 -0
  368. package/src/components/layout/app-shell-wrapper.tsx +32 -0
  369. package/src/components/layout/app-shell.test.tsx +155 -0
  370. package/src/components/layout/app-shell.tsx +100 -0
  371. package/src/components/shared/headers/app-header-configurator.tsx +42 -0
  372. package/src/components/shared/headers/app-header.tsx +103 -0
  373. package/src/components/shared/headers/header-user-menu.tsx +247 -0
  374. package/src/components/shared/headers/index.ts +44 -0
  375. package/src/components/shared/headers/page-header.tsx +25 -0
  376. package/src/components/shared/notifications/__tests__/notification-bell.test.tsx +159 -0
  377. package/src/components/shared/notifications/__tests__/notification-dropdown.test.tsx +296 -0
  378. package/src/components/shared/notifications/__tests__/notification-item.test.tsx +328 -0
  379. package/src/components/shared/notifications/index.ts +45 -0
  380. package/src/components/shared/notifications/notification-actions.tsx +295 -0
  381. package/src/components/shared/notifications/notification-bell-button.tsx +77 -0
  382. package/src/components/shared/notifications/notification-dropdown.tsx +160 -0
  383. package/src/components/shared/notifications/notification-group-item.tsx +268 -0
  384. package/src/components/shared/notifications/notification-item.tsx +193 -0
  385. package/src/components/shared/notifications/notification-load-more.tsx +50 -0
  386. package/src/components/shared/notifications/notification-panel.tsx +49 -0
  387. package/src/components/shared/notifications/utils.tsx +127 -0
  388. package/src/components/shared/permission-guard/index.ts +1 -0
  389. package/src/components/shared/permission-guard/permission-tooltip.tsx +45 -0
  390. package/src/components/shared/relative-time.tsx +53 -0
  391. package/src/components/shared/section-container.tsx +32 -0
  392. package/src/components/shared/service-status-banner.tsx +121 -0
  393. package/src/components/shared/settings-sidebar/index.ts +2 -0
  394. package/src/components/shared/settings-sidebar/team-setting-aside.tsx +97 -0
  395. package/src/components/shared/settings-sidebar/user-settings-aside.tsx +66 -0
  396. package/src/components/shared/sidebar/app-sidebar.tsx +146 -0
  397. package/src/components/shared/sidebar/index.ts +36 -0
  398. package/src/components/shared/sidebar/sidebar-main.tsx +81 -0
  399. package/src/components/shared/sidebar/sidebar-project.tsx +61 -0
  400. package/src/components/shared/sidebar/sidebar-team-avatar.tsx +126 -0
  401. package/src/components/shared/sidebar/sidebar-team-switcher.tsx +185 -0
  402. package/src/components/shared/sidebar/type.ts +97 -0
  403. package/src/components/ui/alert-dialog.tsx +157 -0
  404. package/src/components/ui/alert.tsx +66 -0
  405. package/src/components/ui/avatar-upload.tsx +147 -0
  406. package/src/components/ui/avatar.tsx +53 -0
  407. package/src/components/ui/badge.tsx +46 -0
  408. package/src/components/ui/breadcrumb.tsx +109 -0
  409. package/src/components/ui/button.tsx +60 -0
  410. package/src/components/ui/card.tsx +92 -0
  411. package/src/components/ui/checkbox.tsx +32 -0
  412. package/src/components/ui/collapsible.tsx +33 -0
  413. package/src/components/ui/command.tsx +184 -0
  414. package/src/components/ui/dialog.tsx +143 -0
  415. package/src/components/ui/dropdown-menu.tsx +257 -0
  416. package/src/components/ui/empty.tsx +104 -0
  417. package/src/components/ui/field.tsx +244 -0
  418. package/src/components/ui/image-cropper-dialog.tsx +167 -0
  419. package/src/components/ui/input.tsx +21 -0
  420. package/src/components/ui/label.tsx +24 -0
  421. package/src/components/ui/optimized-image.tsx +220 -0
  422. package/src/components/ui/pagination.tsx +127 -0
  423. package/src/components/ui/popover.tsx +48 -0
  424. package/src/components/ui/progress.tsx +31 -0
  425. package/src/components/ui/radio-group.tsx +45 -0
  426. package/src/components/ui/scroll-area.tsx +58 -0
  427. package/src/components/ui/select.tsx +187 -0
  428. package/src/components/ui/separator.tsx +28 -0
  429. package/src/components/ui/sheet.tsx +139 -0
  430. package/src/components/ui/sidebar.tsx +733 -0
  431. package/src/components/ui/skeleton.tsx +13 -0
  432. package/src/components/ui/sonner.tsx +40 -0
  433. package/src/components/ui/spinner.tsx +16 -0
  434. package/src/components/ui/switch.tsx +31 -0
  435. package/src/components/ui/table.tsx +116 -0
  436. package/src/components/ui/tabs.tsx +66 -0
  437. package/src/components/ui/textarea.tsx +23 -0
  438. package/src/components/ui/tooltip.tsx +61 -0
  439. package/src/config/__tests__/workspace.property.test.ts +40 -0
  440. package/src/config/auth.ts +62 -0
  441. package/src/config/integrations.ts +126 -0
  442. package/src/config/quotas.ts +20 -0
  443. package/src/config/roles.ts +463 -0
  444. package/src/config/settings-nav.ts +39 -0
  445. package/src/config/team-settings-nav.ts +37 -0
  446. package/src/config/user-settings-nav.ts +42 -0
  447. package/src/config/version.ts +1 -0
  448. package/src/config/workspace.ts +64 -0
  449. package/src/features/annotations/README.md +283 -0
  450. package/src/features/annotations/api/annotations-api.ts +194 -0
  451. package/src/features/annotations/api/comments-api.ts +147 -0
  452. package/src/features/annotations/api/index.ts +71 -0
  453. package/src/features/annotations/api/save-annotation.ts +150 -0
  454. package/src/features/annotations/api/schemas.ts +142 -0
  455. package/src/features/annotations/components/annotated-attachment-view.tsx +576 -0
  456. package/src/features/annotations/components/annotation-action-sheet.tsx +140 -0
  457. package/src/features/annotations/components/annotation-annotations-panel.tsx +213 -0
  458. package/src/features/annotations/components/annotation-box.tsx +539 -0
  459. package/src/features/annotations/components/annotation-canvas.tsx +534 -0
  460. package/src/features/annotations/components/annotation-comment-input.tsx +145 -0
  461. package/src/features/annotations/components/annotation-context-menu.tsx +164 -0
  462. package/src/features/annotations/components/annotation-drawer.tsx +231 -0
  463. package/src/features/annotations/components/annotation-layer.tsx +271 -0
  464. package/src/features/annotations/components/annotation-pin.tsx +318 -0
  465. package/src/features/annotations/components/annotation-popover.tsx +562 -0
  466. package/src/features/annotations/components/annotation-thread-panel.tsx +485 -0
  467. package/src/features/annotations/components/annotation-thread-preview.tsx +195 -0
  468. package/src/features/annotations/components/annotation-toolbar.tsx +244 -0
  469. package/src/features/annotations/components/keyboard-shortcuts-modal.tsx +79 -0
  470. package/src/features/annotations/docs/ANNOTATIONS_ARCHITECTURE.md +67 -0
  471. package/src/features/annotations/docs/ANNOTATION_SAVE_ARCHITECTURE.md +422 -0
  472. package/src/features/annotations/docs/ANNOTATION_SAVE_FEATURE.md +408 -0
  473. package/src/features/annotations/docs/BOX_ANNOTATION_GUIDE.md +542 -0
  474. package/src/features/annotations/docs/NEXTSTEP.md +28 -0
  475. package/src/features/annotations/docs/STALE_CLOSURE_FIX.md +344 -0
  476. package/src/features/annotations/docs/UNDO_REDO_QUICK_START.md +545 -0
  477. package/src/features/annotations/docs/local_first_canvas_autosave_architecture.md +674 -0
  478. package/src/features/annotations/examples/complete-example.tsx +266 -0
  479. package/src/features/annotations/examples/save-annotation-example.tsx +309 -0
  480. package/src/features/annotations/hooks/__tests__/use-annotation-permissions.property.test.tsx +493 -0
  481. package/src/features/annotations/hooks/index.ts +36 -0
  482. package/src/features/annotations/hooks/use-annotation-batch-save.ts +109 -0
  483. package/src/features/annotations/hooks/use-annotation-comments.ts +353 -0
  484. package/src/features/annotations/hooks/use-annotation-drafts.ts +137 -0
  485. package/src/features/annotations/hooks/use-annotation-edit-state.ts +99 -0
  486. package/src/features/annotations/hooks/use-annotation-history-tracker.ts +159 -0
  487. package/src/features/annotations/hooks/use-annotation-integration.ts +916 -0
  488. package/src/features/annotations/hooks/use-annotation-permissions.ts +210 -0
  489. package/src/features/annotations/hooks/use-annotation-popover.ts +175 -0
  490. package/src/features/annotations/hooks/use-annotation-save.ts +208 -0
  491. package/src/features/annotations/hooks/use-annotation-tools.ts +237 -0
  492. package/src/features/annotations/hooks/use-annotations-with-history.ts +332 -0
  493. package/src/features/annotations/hooks/use-auto-save.ts +94 -0
  494. package/src/features/annotations/index.ts +111 -0
  495. package/src/features/annotations/types/annotation.ts +201 -0
  496. package/src/features/annotations/types/index.ts +28 -0
  497. package/src/features/annotations/utils/history-manager.ts +73 -0
  498. package/src/features/annotations/utils/index.ts +2 -0
  499. package/src/features/annotations/utils/map-attachments-to-threads.ts +28 -0
  500. package/src/features/annotations/utils/position-comment-input.ts +136 -0
  501. package/src/features/annotations/utils/re-sequence-labels.ts +92 -0
  502. package/src/features/annotations/utils/validate-annotation-label.ts +120 -0
  503. package/src/features/auth/api/types.ts +101 -0
  504. package/src/features/auth/components/__tests__/role-gate.test.tsx +448 -0
  505. package/src/features/auth/components/__tests__/social-login-buttons.test.tsx +313 -0
  506. package/src/features/auth/components/auth-card.tsx +36 -0
  507. package/src/features/auth/components/forgot-password-form.tsx +115 -0
  508. package/src/features/auth/components/index.ts +14 -0
  509. package/src/features/auth/components/invite-code-input.tsx +155 -0
  510. package/src/features/auth/components/invited-user-form.tsx +309 -0
  511. package/src/features/auth/components/onboarding-form.tsx +195 -0
  512. package/src/features/auth/components/password-strength-indicator.tsx +113 -0
  513. package/src/features/auth/components/reset-password-form.tsx +138 -0
  514. package/src/features/auth/components/role-gate.tsx +124 -0
  515. package/src/features/auth/components/self-registration-choice.tsx +153 -0
  516. package/src/features/auth/components/sign-in-form.tsx +159 -0
  517. package/src/features/auth/components/sign-up-form.tsx +158 -0
  518. package/src/features/auth/components/social-login-buttons.tsx +219 -0
  519. package/src/features/auth/hooks/__tests__/use-onboarding.test.tsx +109 -0
  520. package/src/features/auth/hooks/__tests__/use-session.test.tsx +160 -0
  521. package/src/features/auth/hooks/index.ts +15 -0
  522. package/src/features/auth/hooks/use-accept-invitation.ts +194 -0
  523. package/src/features/auth/hooks/use-delete-account.ts +86 -0
  524. package/src/features/auth/hooks/use-force-verify.ts +89 -0
  525. package/src/features/auth/hooks/use-forgot-password.ts +144 -0
  526. package/src/features/auth/hooks/use-link-account.ts +78 -0
  527. package/src/features/auth/hooks/use-linked-accounts.ts +88 -0
  528. package/src/features/auth/hooks/use-onboarding.ts +159 -0
  529. package/src/features/auth/hooks/use-resend-verification.ts +139 -0
  530. package/src/features/auth/hooks/use-reset-password.ts +151 -0
  531. package/src/features/auth/hooks/use-reset-rate-limit.ts +56 -0
  532. package/src/features/auth/hooks/use-self-registration.ts +202 -0
  533. package/src/features/auth/hooks/use-session.ts +81 -0
  534. package/src/features/auth/hooks/use-sessions.ts +59 -0
  535. package/src/features/auth/hooks/use-sign-in.ts +234 -0
  536. package/src/features/auth/hooks/use-sign-out.ts +88 -0
  537. package/src/features/auth/hooks/use-sign-up.ts +194 -0
  538. package/src/features/auth/hooks/use-unlink-account.ts +100 -0
  539. package/src/features/auth/hooks/use-verify-email-token.ts +125 -0
  540. package/src/features/auth/index.ts +75 -0
  541. package/src/features/auth/screens/forgot-password-screen.tsx +33 -0
  542. package/src/features/auth/screens/index.ts +7 -0
  543. package/src/features/auth/screens/onboarding-screen.tsx +49 -0
  544. package/src/features/auth/screens/reset-password-screen.tsx +33 -0
  545. package/src/features/auth/screens/sign-in-screen.tsx +61 -0
  546. package/src/features/auth/screens/sign-up-screen.tsx +37 -0
  547. package/src/features/auth/screens/verify-email-confirm-screen.tsx +286 -0
  548. package/src/features/auth/screens/verify-email-screen.tsx +146 -0
  549. package/src/features/auth/types/index.ts +14 -0
  550. package/src/features/auth/utils/__tests__/validators.test.ts +331 -0
  551. package/src/features/auth/utils/password-strength.ts +129 -0
  552. package/src/features/auth/utils/validators.ts +124 -0
  553. package/src/features/email-preview/actions/render-email.ts +21 -0
  554. package/src/features/email-preview/screens/email-preview-screen.tsx +81 -0
  555. package/src/features/folder-scaffold-template/index.ts +0 -0
  556. package/src/features/instance-settings/components/index.ts +6 -0
  557. package/src/features/instance-settings/components/instance-settings-form.tsx +180 -0
  558. package/src/features/instance-settings/components/instance-status-display.tsx +158 -0
  559. package/src/features/instance-settings/index.ts +7 -0
  560. package/src/features/instance-settings/screens/index.ts +5 -0
  561. package/src/features/instance-settings/screens/instance-settings-screen.tsx +59 -0
  562. package/src/features/issues/README.md +330 -0
  563. package/src/features/issues/api/create-issue.ts +19 -0
  564. package/src/features/issues/api/delete-issue.ts +27 -0
  565. package/src/features/issues/api/get-issue-activities.ts +58 -0
  566. package/src/features/issues/api/get-issue-details.ts +25 -0
  567. package/src/features/issues/api/get-project-issues-server.ts +44 -0
  568. package/src/features/issues/api/get-project-issues.ts +21 -0
  569. package/src/features/issues/api/index.ts +44 -0
  570. package/src/features/issues/api/update-issue.ts +31 -0
  571. package/src/features/issues/api/upload-attachment.ts +81 -0
  572. package/src/features/issues/components/activity-timeline.tsx +440 -0
  573. package/src/features/issues/components/canvas-state-indicator.tsx +90 -0
  574. package/src/features/issues/components/centered-canvas-view.tsx +739 -0
  575. package/src/features/issues/components/image-selector.tsx +123 -0
  576. package/src/features/issues/components/image-upload-zone.tsx +262 -0
  577. package/src/features/issues/components/infinite-canvas-background.tsx +163 -0
  578. package/src/features/issues/components/inline-editable-select.tsx +173 -0
  579. package/src/features/issues/components/inline-editable-text.tsx +225 -0
  580. package/src/features/issues/components/inline-editable-textarea.tsx +219 -0
  581. package/src/features/issues/components/inline-editable-user-select.tsx +202 -0
  582. package/src/features/issues/components/issue-deletion-dialog.tsx +142 -0
  583. package/src/features/issues/components/issue-details-panel.tsx +101 -0
  584. package/src/features/issues/components/issues-create-dialog.tsx +578 -0
  585. package/src/features/issues/components/issues-list-filter.tsx +312 -0
  586. package/src/features/issues/components/issues-list.tsx +151 -0
  587. package/src/features/issues/components/issues-priority-badge.tsx +77 -0
  588. package/src/features/issues/components/issues-status-badge.tsx +100 -0
  589. package/src/features/issues/components/metadata-section.tsx +389 -0
  590. package/src/features/issues/components/optimized-attachment-view.tsx +528 -0
  591. package/src/features/issues/components/optimized-image.tsx +257 -0
  592. package/src/features/issues/components/panel-header.tsx +186 -0
  593. package/src/features/issues/components/preload.ts +31 -0
  594. package/src/features/issues/components/priority-selector.tsx +101 -0
  595. package/src/features/issues/components/responsive-issue-layout-skeleton.tsx +139 -0
  596. package/src/features/issues/components/responsive-issue-layout.tsx +617 -0
  597. package/src/features/issues/components/status-selector.tsx +320 -0
  598. package/src/features/issues/components/type-selector.tsx +102 -0
  599. package/src/features/issues/components/upload-progress-overlay.tsx +35 -0
  600. package/src/features/issues/components/uploaded-image-preview.tsx +173 -0
  601. package/src/features/issues/components/workflow-control.tsx +318 -0
  602. package/src/features/issues/components/zoom-controls.tsx +150 -0
  603. package/src/features/issues/config/index.ts +47 -0
  604. package/src/features/issues/config/options.ts +323 -0
  605. package/src/features/issues/config/workflow.ts +102 -0
  606. package/src/features/issues/docs/ARCHITECTURE_DIAGRAM.md +321 -0
  607. package/src/features/issues/docs/BACKEND_ARCHITECTURE.md +194 -0
  608. package/src/features/issues/docs/IMAGE_COMPONENTS_ARCHITECTURE.md +363 -0
  609. package/src/features/issues/docs/IMAGE_COMPONENTS_IMPORTS.md +412 -0
  610. package/src/features/issues/docs/IMPLEMENTATION_CHECKLIST.md +210 -0
  611. package/src/features/issues/docs/ROUTE_SETUP_COMPLETE.md +242 -0
  612. package/src/features/issues/hooks/index.ts +78 -0
  613. package/src/features/issues/hooks/use-canvas-transform.ts +255 -0
  614. package/src/features/issues/hooks/use-create-issue.ts +28 -0
  615. package/src/features/issues/hooks/use-elastic-scroll.ts +296 -0
  616. package/src/features/issues/hooks/use-issue-activities.ts +71 -0
  617. package/src/features/issues/hooks/use-issue-delete.ts +84 -0
  618. package/src/features/issues/hooks/use-issue-details.ts +70 -0
  619. package/src/features/issues/hooks/use-issue-filters.ts +50 -0
  620. package/src/features/issues/hooks/use-issue-update.ts +93 -0
  621. package/src/features/issues/hooks/use-keyboard-shortcuts.ts +104 -0
  622. package/src/features/issues/hooks/use-optimized-image.ts +228 -0
  623. package/src/features/issues/hooks/use-project-issues.ts +14 -0
  624. package/src/features/issues/index.ts +65 -0
  625. package/src/features/issues/screens/issue-details-screen.tsx +207 -0
  626. package/src/features/issues/screens/issue-details-skeletons.tsx +295 -0
  627. package/src/features/issues/screens/issue-share-screen.tsx +56 -0
  628. package/src/features/issues/types/index.ts +48 -0
  629. package/src/features/issues/types/issue.ts +291 -0
  630. package/src/features/issues/utils/filter-issues.ts +141 -0
  631. package/src/features/issues/utils/index.ts +14 -0
  632. package/src/features/legal/index.ts +1 -0
  633. package/src/features/legal/screens/privacy-policy-screen.tsx +307 -0
  634. package/src/features/notifications/api/get-notifications.ts +58 -0
  635. package/src/features/notifications/api/get-unread-count.ts +37 -0
  636. package/src/features/notifications/api/index.ts +35 -0
  637. package/src/features/notifications/api/mark-all-as-read.ts +37 -0
  638. package/src/features/notifications/api/mark-as-read.ts +41 -0
  639. package/src/features/notifications/api/types.ts +109 -0
  640. package/src/features/notifications/hooks/__tests__/use-notification-subscription.test.ts +206 -0
  641. package/src/features/notifications/hooks/index.ts +28 -0
  642. package/src/features/notifications/hooks/use-mark-all-as-read.ts +106 -0
  643. package/src/features/notifications/hooks/use-mark-as-read.ts +106 -0
  644. package/src/features/notifications/hooks/use-notification-subscription.ts +244 -0
  645. package/src/features/notifications/hooks/use-notification-toast.ts +161 -0
  646. package/src/features/notifications/hooks/use-notifications.ts +80 -0
  647. package/src/features/notifications/hooks/use-unread-count.ts +60 -0
  648. package/src/features/notifications/index.ts +48 -0
  649. package/src/features/notifications/utils/group-notifications.ts +152 -0
  650. package/src/features/notifications/utils/index.ts +9 -0
  651. package/src/features/projects/api/create-invitation.ts +45 -0
  652. package/src/features/projects/api/create-project.ts +64 -0
  653. package/src/features/projects/api/delete-project.ts +50 -0
  654. package/src/features/projects/api/get-project-activities.ts +43 -0
  655. package/src/features/projects/api/get-project-members.ts +53 -0
  656. package/src/features/projects/api/get-project.ts +49 -0
  657. package/src/features/projects/api/get-projects.ts +61 -0
  658. package/src/features/projects/api/index.ts +27 -0
  659. package/src/features/projects/api/join-project.ts +52 -0
  660. package/src/features/projects/api/leave-project.ts +51 -0
  661. package/src/features/projects/api/list-invitations.ts +36 -0
  662. package/src/features/projects/api/remove-member.ts +60 -0
  663. package/src/features/projects/api/resend-invitation.ts +36 -0
  664. package/src/features/projects/api/revoke-invitation.ts +36 -0
  665. package/src/features/projects/api/types.ts +286 -0
  666. package/src/features/projects/api/update-member-role.ts +70 -0
  667. package/src/features/projects/api/update-project.ts +69 -0
  668. package/src/features/projects/components/__tests__/project-detail-activity-feed.test.tsx +106 -0
  669. package/src/features/projects/components/__tests__/project-invitation-dialog.test.tsx +211 -0
  670. package/src/features/projects/components/__tests__/project-member-manager-dialog.test.tsx +254 -0
  671. package/src/features/projects/components/index.ts +21 -0
  672. package/src/features/projects/components/project-actions.tsx +248 -0
  673. package/src/features/projects/components/project-create-dialog.tsx +410 -0
  674. package/src/features/projects/components/project-detail-activity-feed.tsx +206 -0
  675. package/src/features/projects/components/project-detail-header.tsx +103 -0
  676. package/src/features/projects/components/project-detail-overview.tsx +128 -0
  677. package/src/features/projects/components/project-icon-selector.test.tsx +49 -0
  678. package/src/features/projects/components/project-icon-selector.tsx +76 -0
  679. package/src/features/projects/components/project-invitation-dialog.tsx +368 -0
  680. package/src/features/projects/components/project-issues.tsx +128 -0
  681. package/src/features/projects/components/project-leave-button.tsx +69 -0
  682. package/src/features/projects/components/project-list-card.tsx +246 -0
  683. package/src/features/projects/components/project-list-filters.tsx +320 -0
  684. package/src/features/projects/components/project-member-manager-dialog.tsx +419 -0
  685. package/src/features/projects/components/project-settings-dialog.tsx +204 -0
  686. package/src/features/projects/components/project-stats.tsx +46 -0
  687. package/src/features/projects/components/project-title-section.tsx +78 -0
  688. package/src/features/projects/config/icons.ts +91 -0
  689. package/src/features/projects/hooks/index.ts +28 -0
  690. package/src/features/projects/hooks/use-create-invitation.ts +83 -0
  691. package/src/features/projects/hooks/use-create-project.ts +77 -0
  692. package/src/features/projects/hooks/use-delete-project.ts +84 -0
  693. package/src/features/projects/hooks/use-join-project.ts +43 -0
  694. package/src/features/projects/hooks/use-leave-project.ts +84 -0
  695. package/src/features/projects/hooks/use-project-activities.ts +39 -0
  696. package/src/features/projects/hooks/use-project-filters.ts +86 -0
  697. package/src/features/projects/hooks/use-project-invitations.ts +66 -0
  698. package/src/features/projects/hooks/use-project-members.ts +57 -0
  699. package/src/features/projects/hooks/use-project.ts +67 -0
  700. package/src/features/projects/hooks/use-projects.ts +49 -0
  701. package/src/features/projects/hooks/use-recent-projects.ts +58 -0
  702. package/src/features/projects/hooks/use-remove-member.ts +89 -0
  703. package/src/features/projects/hooks/use-resend-invitation.ts +68 -0
  704. package/src/features/projects/hooks/use-revoke-invitation.ts +71 -0
  705. package/src/features/projects/hooks/use-team-member-suggestions.ts +133 -0
  706. package/src/features/projects/hooks/use-update-member-role.ts +92 -0
  707. package/src/features/projects/hooks/use-update-project.ts +88 -0
  708. package/src/features/projects/index.ts +91 -0
  709. package/src/features/projects/screens/index.ts +3 -0
  710. package/src/features/projects/screens/invitation-acceptance-screen.tsx +320 -0
  711. package/src/features/projects/screens/project-detail-screen-wrapper.tsx +47 -0
  712. package/src/features/projects/screens/project-detail-screen.tsx +661 -0
  713. package/src/features/projects/screens/projects-list-screen.tsx +161 -0
  714. package/src/features/projects/types/index.ts +59 -0
  715. package/src/features/projects/utils/format-helpers.ts +16 -0
  716. package/src/features/projects/utils/index.ts +2 -0
  717. package/src/features/projects/utils/role-helpers.ts +25 -0
  718. package/src/features/setup/api/complete-setup.ts +21 -0
  719. package/src/features/setup/api/create-admin.ts +21 -0
  720. package/src/features/setup/api/create-first-workspace.ts +21 -0
  721. package/src/features/setup/api/get-instance-status.ts +12 -0
  722. package/src/features/setup/api/get-service-health.ts +12 -0
  723. package/src/features/setup/api/index.ts +44 -0
  724. package/src/features/setup/api/save-instance-config.ts +21 -0
  725. package/src/features/setup/api/types.ts +122 -0
  726. package/src/features/setup/components/__tests__/setup-wizard-ui.test.tsx +362 -0
  727. package/src/features/setup/components/admin-account-step.tsx +205 -0
  728. package/src/features/setup/components/first-workspace-step.tsx +120 -0
  729. package/src/features/setup/components/index.ts +9 -0
  730. package/src/features/setup/components/instance-config-step.tsx +107 -0
  731. package/src/features/setup/components/mail-config-step.tsx +205 -0
  732. package/src/features/setup/components/sample-data-step.tsx +131 -0
  733. package/src/features/setup/components/service-health-step.tsx +180 -0
  734. package/src/features/setup/components/service-status-badge.tsx +50 -0
  735. package/src/features/setup/components/setup-progress.tsx +103 -0
  736. package/src/features/setup/components/setup-wizard.tsx +169 -0
  737. package/src/features/setup/hooks/index.ts +12 -0
  738. package/src/features/setup/hooks/use-complete-setup.ts +23 -0
  739. package/src/features/setup/hooks/use-create-admin.ts +25 -0
  740. package/src/features/setup/hooks/use-create-first-workspace.ts +24 -0
  741. package/src/features/setup/hooks/use-instance-status.ts +21 -0
  742. package/src/features/setup/hooks/use-save-instance-config.ts +23 -0
  743. package/src/features/setup/hooks/use-service-health.ts +21 -0
  744. package/src/features/setup/hooks/use-setup-wizard.ts +152 -0
  745. package/src/features/setup/hooks/use-workspace-mode.ts +19 -0
  746. package/src/features/setup/index.ts +30 -0
  747. package/src/features/setup/screens/index.ts +1 -0
  748. package/src/features/setup/screens/setup-screen.tsx +40 -0
  749. package/src/features/setup/types/index.ts +69 -0
  750. package/src/features/setup/utils/index.ts +78 -0
  751. package/src/features/team-settings/components/index.ts +39 -0
  752. package/src/features/team-settings/components/loading-states.tsx +296 -0
  753. package/src/features/team-settings/components/permission-guard.tsx +23 -0
  754. package/src/features/team-settings/components/settings-card.tsx +22 -0
  755. package/src/features/team-settings/components/settings-context-provider.tsx +51 -0
  756. package/src/features/team-settings/components/settings-error-boundary.tsx +366 -0
  757. package/src/features/team-settings/components/settings-navigation.tsx +87 -0
  758. package/src/features/team-settings/components/settings-section.tsx +23 -0
  759. package/src/features/team-settings/components/team-danger-zone.tsx +275 -0
  760. package/src/features/team-settings/components/team-information-form.tsx +116 -0
  761. package/src/features/team-settings/components/team-invitations-list.tsx +463 -0
  762. package/src/features/team-settings/components/team-members-list.tsx +342 -0
  763. package/src/features/team-settings/components/team-permission-guard.tsx +56 -0
  764. package/src/features/team-settings/components/team-setting-integrations.tsx +27 -0
  765. package/src/features/team-settings/components/team-setting-member.tsx +28 -0
  766. package/src/features/team-settings/components/team-settings-general.tsx +131 -0
  767. package/src/features/team-settings/components/transfer-ownership-modal.tsx +164 -0
  768. package/src/features/team-settings/components/unauthorized-access.tsx +52 -0
  769. package/src/features/team-settings/hooks/__tests__/use-team-settings.test.tsx +139 -0
  770. package/src/features/team-settings/hooks/index.ts +1 -0
  771. package/src/features/team-settings/hooks/use-team-settings.ts +148 -0
  772. package/src/features/team-settings/hooks/use-transfer-ownership.ts +45 -0
  773. package/src/features/team-settings/index.ts +25 -0
  774. package/src/features/team-settings/screens/index.ts +1 -0
  775. package/src/features/team-settings/screens/team-settings-screen.tsx +33 -0
  776. package/src/features/team-settings/types/index.ts +78 -0
  777. package/src/features/team-settings/utils/index.ts +6 -0
  778. package/src/features/team-settings/utils/mock-data.ts +40 -0
  779. package/src/features/teams/api/cancel-invitation.ts +25 -0
  780. package/src/features/teams/api/create-invitation.ts +28 -0
  781. package/src/features/teams/api/create-team.ts +14 -0
  782. package/src/features/teams/api/delete-team.ts +19 -0
  783. package/src/features/teams/api/get-invitations.ts +25 -0
  784. package/src/features/teams/api/get-team-members.ts +25 -0
  785. package/src/features/teams/api/get-team.ts +13 -0
  786. package/src/features/teams/api/get-teams.ts +19 -0
  787. package/src/features/teams/api/index.ts +49 -0
  788. package/src/features/teams/api/leave-team.ts +22 -0
  789. package/src/features/teams/api/remove-member.ts +25 -0
  790. package/src/features/teams/api/resend-invitation.ts +26 -0
  791. package/src/features/teams/api/switch-team.ts +13 -0
  792. package/src/features/teams/api/types.ts +122 -0
  793. package/src/features/teams/api/update-member-roles.ts +28 -0
  794. package/src/features/teams/api/update-team.ts +17 -0
  795. package/src/features/teams/components/create-team-dialog.tsx +105 -0
  796. package/src/features/teams/hooks/__tests__/cache-invalidation.property.test.tsx +268 -0
  797. package/src/features/teams/hooks/index.ts +35 -0
  798. package/src/features/teams/hooks/use-can-manage-members.ts +21 -0
  799. package/src/features/teams/hooks/use-can-manage-team.ts +21 -0
  800. package/src/features/teams/hooks/use-cancel-invitation.ts +52 -0
  801. package/src/features/teams/hooks/use-create-invitation.ts +59 -0
  802. package/src/features/teams/hooks/use-create-team.ts +38 -0
  803. package/src/features/teams/hooks/use-delete-team.ts +43 -0
  804. package/src/features/teams/hooks/use-invitations.ts +31 -0
  805. package/src/features/teams/hooks/use-leave-team.ts +43 -0
  806. package/src/features/teams/hooks/use-remove-member.ts +58 -0
  807. package/src/features/teams/hooks/use-resend-invitation.ts +52 -0
  808. package/src/features/teams/hooks/use-switch-team.ts +41 -0
  809. package/src/features/teams/hooks/use-team-members.ts +31 -0
  810. package/src/features/teams/hooks/use-team-permissions.ts +102 -0
  811. package/src/features/teams/hooks/use-team.ts +30 -0
  812. package/src/features/teams/hooks/use-teams.ts +26 -0
  813. package/src/features/teams/hooks/use-update-member-roles.ts +64 -0
  814. package/src/features/teams/hooks/use-update-team.ts +47 -0
  815. package/src/features/teams/index.ts +111 -0
  816. package/src/features/user-settings/actions/set-password.ts +63 -0
  817. package/src/features/user-settings/api/index.ts +16 -0
  818. package/src/features/user-settings/components/__tests__/security-settings.test.tsx +125 -0
  819. package/src/features/user-settings/components/delete-account-dialog.tsx +185 -0
  820. package/src/features/user-settings/components/index.ts +13 -0
  821. package/src/features/user-settings/components/integrations-list.tsx +152 -0
  822. package/src/features/user-settings/components/notification-preferences.tsx +112 -0
  823. package/src/features/user-settings/components/other-settings.tsx +126 -0
  824. package/src/features/user-settings/components/password-section.tsx +297 -0
  825. package/src/features/user-settings/components/security-settings.tsx +184 -0
  826. package/src/features/user-settings/components/user-preferences.tsx +146 -0
  827. package/src/features/user-settings/hooks/index.ts +8 -0
  828. package/src/features/user-settings/hooks/use-notification-preferences.ts +65 -0
  829. package/src/features/user-settings/hooks/use-user-preferences.ts +52 -0
  830. package/src/features/user-settings/index.ts +22 -0
  831. package/src/features/user-settings/screens/index.ts +11 -0
  832. package/src/features/user-settings/screens/integrations-screen.tsx +24 -0
  833. package/src/features/user-settings/screens/notifications-screen.tsx +29 -0
  834. package/src/features/user-settings/screens/other-settings-screen.tsx +24 -0
  835. package/src/features/user-settings/screens/setting-preferences-screen.tsx +24 -0
  836. package/src/features/user-settings/screens/user-settings-screen.tsx +23 -0
  837. package/src/features/user-settings/types/index.ts +42 -0
  838. package/src/features/user-settings/utils/index.ts +8 -0
  839. package/src/hooks/use-long-press.ts +196 -0
  840. package/src/hooks/use-media-upload.ts +95 -0
  841. package/src/hooks/use-mobile.ts +19 -0
  842. package/src/hooks/use-team.ts +32 -0
  843. package/src/instrumentation.ts +14 -0
  844. package/src/lib/__tests__/auth-config.test.ts +166 -0
  845. package/src/lib/__tests__/config.test.ts +42 -0
  846. package/src/lib/__tests__/db.test.ts +101 -0
  847. package/src/lib/__tests__/env.property.test.ts +197 -0
  848. package/src/lib/__tests__/env.test.ts +177 -0
  849. package/src/lib/__tests__/health-check.test.ts +87 -0
  850. package/src/lib/__tests__/oauth-errors.test.ts +184 -0
  851. package/src/lib/__tests__/oauth-redirect.test.ts +224 -0
  852. package/src/lib/__tests__/oauth-scopes.test.ts +268 -0
  853. package/src/lib/__tests__/storage.test.ts +58 -0
  854. package/src/lib/__tests__/url-validator.test.ts +232 -0
  855. package/src/lib/api-client.ts +93 -0
  856. package/src/lib/auth-client.ts +5 -0
  857. package/src/lib/auth-config.ts +146 -0
  858. package/src/lib/auth.ts +209 -0
  859. package/src/lib/config.ts +228 -0
  860. package/src/lib/cors.ts +96 -0
  861. package/src/lib/date.ts +16 -0
  862. package/src/lib/db.ts +88 -0
  863. package/src/lib/env.ts +489 -0
  864. package/src/lib/feedback.ts +29 -0
  865. package/src/lib/health-check.ts +229 -0
  866. package/src/lib/logger.ts +204 -0
  867. package/src/lib/oauth-errors.ts +138 -0
  868. package/src/lib/performance.ts +367 -0
  869. package/src/lib/query-server.ts +35 -0
  870. package/src/lib/query.tsx +43 -0
  871. package/src/lib/resend.ts +36 -0
  872. package/src/lib/security-headers.ts +165 -0
  873. package/src/lib/setup-status.ts +34 -0
  874. package/src/lib/storage.ts +150 -0
  875. package/src/lib/supabase-client.ts +58 -0
  876. package/src/lib/testing/test-db.ts +75 -0
  877. package/src/lib/url-validator.ts +168 -0
  878. package/src/lib/utils.ts +6 -0
  879. package/src/mocks/README.md +42 -0
  880. package/src/mocks/activity.fixtures.ts +281 -0
  881. package/src/mocks/annotation.fixtures.ts +325 -0
  882. package/src/mocks/attachment.fixtures.ts +80 -0
  883. package/src/mocks/email.fixtures.ts +61 -0
  884. package/src/mocks/index.ts +33 -0
  885. package/src/mocks/issue.fixtures.ts +364 -0
  886. package/src/mocks/landing.fixtures.ts +118 -0
  887. package/src/mocks/notification.fixtures.ts +111 -0
  888. package/src/mocks/project-activity.fixtures.ts +69 -0
  889. package/src/mocks/project-invitation.fixtures.ts +95 -0
  890. package/src/mocks/project-member.fixtures.ts +93 -0
  891. package/src/mocks/project.fixtures.ts +249 -0
  892. package/src/mocks/settings.fixtures.ts +56 -0
  893. package/src/mocks/share.fixtures.ts +48 -0
  894. package/src/mocks/team-member.fixtures.ts +147 -0
  895. package/src/mocks/team.fixtures.ts +35 -0
  896. package/src/mocks/user-settings.fixtures.ts +77 -0
  897. package/src/mocks/user.fixtures.ts +21 -0
  898. package/src/providers/theme-provider.tsx +11 -0
  899. package/src/proxy/__tests__/proxy-setup-gate.test.ts +43 -0
  900. package/src/proxy.ts +169 -0
  901. package/src/server/annotations/__tests__/annotation-limit.property.test.ts +291 -0
  902. package/src/server/annotations/__tests__/attachment-deletion-cascade.property.test.ts +385 -0
  903. package/src/server/annotations/__tests__/comment-delete-authorization.property.test.ts +419 -0
  904. package/src/server/annotations/__tests__/sanitize.property.test.ts +302 -0
  905. package/src/server/annotations/annotation-service.ts +305 -0
  906. package/src/server/annotations/comment-service.ts +288 -0
  907. package/src/server/annotations/index.ts +61 -0
  908. package/src/server/annotations/permission-utils.ts +125 -0
  909. package/src/server/annotations/sanitize.ts +134 -0
  910. package/src/server/annotations/types.ts +161 -0
  911. package/src/server/audit/audit-service.ts +85 -0
  912. package/src/server/audit/index.ts +11 -0
  913. package/src/server/audit/types.ts +75 -0
  914. package/src/server/auth/__tests__/README.md +368 -0
  915. package/src/server/auth/__tests__/account-linking.integration.test.ts +410 -0
  916. package/src/server/auth/__tests__/auth-integration.test.ts +811 -0
  917. package/src/server/auth/__tests__/cookies.test.ts +337 -0
  918. package/src/server/auth/__tests__/login.property.test.ts +428 -0
  919. package/src/server/auth/__tests__/oauth-integration.test.ts +555 -0
  920. package/src/server/auth/__tests__/password.test.ts +194 -0
  921. package/src/server/auth/__tests__/rate-limiter.test.ts +450 -0
  922. package/src/server/auth/__tests__/rbac.test.ts +474 -0
  923. package/src/server/auth/__tests__/session.test.ts +599 -0
  924. package/src/server/auth/__tests__/signup.property.test.ts +224 -0
  925. package/src/server/auth/__tests__/token-encryption.test.ts +171 -0
  926. package/src/server/auth/__tests__/tokens.test.ts +476 -0
  927. package/src/server/auth/__tests__/verify-email.property.test.ts +372 -0
  928. package/src/server/auth/cookies.ts +184 -0
  929. package/src/server/auth/password.ts +94 -0
  930. package/src/server/auth/rate-limiter.ts +257 -0
  931. package/src/server/auth/rbac.ts +1168 -0
  932. package/src/server/auth/session.ts +392 -0
  933. package/src/server/auth/token-encryption.ts +201 -0
  934. package/src/server/auth/tokens.ts +397 -0
  935. package/src/server/db/schema/account.ts +21 -0
  936. package/src/server/db/schema/annotation-read-status.ts +57 -0
  937. package/src/server/db/schema/better-auth-verifications.ts +27 -0
  938. package/src/server/db/schema/email-jobs.ts +24 -0
  939. package/src/server/db/schema/index.ts +20 -0
  940. package/src/server/db/schema/instance-settings.ts +42 -0
  941. package/src/server/db/schema/issue-activities.ts +97 -0
  942. package/src/server/db/schema/issue-attachments.ts +101 -0
  943. package/src/server/db/schema/issues.ts +139 -0
  944. package/src/server/db/schema/notifications.ts +119 -0
  945. package/src/server/db/schema/project-activities.ts +116 -0
  946. package/src/server/db/schema/project-invitations.ts +34 -0
  947. package/src/server/db/schema/project-members.ts +29 -0
  948. package/src/server/db/schema/projects.ts +46 -0
  949. package/src/server/db/schema/sessions.ts +17 -0
  950. package/src/server/db/schema/team-invitations.ts +22 -0
  951. package/src/server/db/schema/team-members.ts +18 -0
  952. package/src/server/db/schema/teams.ts +15 -0
  953. package/src/server/db/schema/user-roles.ts +14 -0
  954. package/src/server/db/schema/users.ts +17 -0
  955. package/src/server/db/schema/verification-tokens.ts +15 -0
  956. package/src/server/email/README.md +258 -0
  957. package/src/server/email/__tests__/client.property.test.ts +218 -0
  958. package/src/server/email/__tests__/payload.property.test.ts +205 -0
  959. package/src/server/email/__tests__/queue.test.ts +407 -0
  960. package/src/server/email/__tests__/render-template.test.tsx +172 -0
  961. package/src/server/email/client.ts +70 -0
  962. package/src/server/email/index.ts +20 -0
  963. package/src/server/email/providers/console-provider.ts +43 -0
  964. package/src/server/email/providers/index.ts +5 -0
  965. package/src/server/email/providers/resend-provider.ts +59 -0
  966. package/src/server/email/providers/smtp-provider.ts +65 -0
  967. package/src/server/email/queue.ts +365 -0
  968. package/src/server/email/render-template.tsx +117 -0
  969. package/src/server/email/templates/layout.tsx +66 -0
  970. package/src/server/email/templates/ownership-transfer-email.tsx +75 -0
  971. package/src/server/email/templates/password-reset-email.tsx +72 -0
  972. package/src/server/email/templates/project-invitation-email.tsx +70 -0
  973. package/src/server/email/templates/security-alert-email.tsx +161 -0
  974. package/src/server/email/templates/team-invitation-email.tsx +68 -0
  975. package/src/server/email/templates/verification-email.tsx +61 -0
  976. package/src/server/email/templates/welcome-email.tsx +60 -0
  977. package/src/server/email/worker.ts +182 -0
  978. package/src/server/issues/activity-service.ts +422 -0
  979. package/src/server/issues/attachment-service.ts +379 -0
  980. package/src/server/issues/index.ts +68 -0
  981. package/src/server/issues/issue-service.ts +569 -0
  982. package/src/server/issues/types.ts +233 -0
  983. package/src/server/notifications/__tests__/notification-service.integration.test.ts +405 -0
  984. package/src/server/notifications/index.ts +13 -0
  985. package/src/server/notifications/notification-service.ts +526 -0
  986. package/src/server/notifications/types.ts +234 -0
  987. package/src/server/projects/__tests__/activity-logging.integration.test.ts +319 -0
  988. package/src/server/projects/__tests__/invitation-email.integration.test.ts +258 -0
  989. package/src/server/projects/__tests__/invitation-service.integration.test.ts +651 -0
  990. package/src/server/projects/__tests__/member-service.property.test.ts +397 -0
  991. package/src/server/projects/__tests__/project-service.property.test.ts +116 -0
  992. package/src/server/projects/activity-service.ts +548 -0
  993. package/src/server/projects/index.ts +11 -0
  994. package/src/server/projects/invitation-service.ts +773 -0
  995. package/src/server/projects/member-service.ts +458 -0
  996. package/src/server/projects/project-service.ts +491 -0
  997. package/src/server/projects/schemas.ts +310 -0
  998. package/src/server/projects/types.ts +166 -0
  999. package/src/server/projects/utils.ts +128 -0
  1000. package/src/server/setup/__tests__/health-check.property.test.ts +70 -0
  1001. package/src/server/setup/__tests__/sample-data.property.test.ts +82 -0
  1002. package/src/server/setup/__tests__/setup.property.test.ts +95 -0
  1003. package/src/server/setup/health-check-service.ts +294 -0
  1004. package/src/server/setup/index.ts +35 -0
  1005. package/src/server/setup/sample-data-service.ts +233 -0
  1006. package/src/server/setup/setup-service.ts +229 -0
  1007. package/src/server/setup/types.ts +96 -0
  1008. package/src/server/teams/__tests__/export.property.test.ts +542 -0
  1009. package/src/server/teams/__tests__/get-members.integration.test.ts +105 -0
  1010. package/src/server/teams/__tests__/invitation-flow.integration.test.ts +402 -0
  1011. package/src/server/teams/__tests__/invitations.property.test.ts +235 -0
  1012. package/src/server/teams/__tests__/member-management-flow.integration.test.ts +306 -0
  1013. package/src/server/teams/__tests__/members.property.test.ts +180 -0
  1014. package/src/server/teams/__tests__/ownership-transfer.property.test.ts +173 -0
  1015. package/src/server/teams/__tests__/resource-limits.property.test.ts +382 -0
  1016. package/src/server/teams/__tests__/team-context-management.integration.test.ts +396 -0
  1017. package/src/server/teams/__tests__/team-context.property.test.ts +854 -0
  1018. package/src/server/teams/__tests__/team-creation-flow.integration.test.ts +310 -0
  1019. package/src/server/teams/__tests__/team-creation.property.test.ts +280 -0
  1020. package/src/server/teams/errors.ts +396 -0
  1021. package/src/server/teams/export-service.ts +383 -0
  1022. package/src/server/teams/index.ts +10 -0
  1023. package/src/server/teams/invitation-service.ts +708 -0
  1024. package/src/server/teams/member-service.ts +334 -0
  1025. package/src/server/teams/resource-limits.ts +211 -0
  1026. package/src/server/teams/slug.ts +58 -0
  1027. package/src/server/teams/team-context.ts +648 -0
  1028. package/src/server/teams/team-service.ts +660 -0
  1029. package/src/server/teams/types.ts +81 -0
  1030. package/src/server/teams/validation.ts +209 -0
  1031. package/src/styles/globals.css +160 -0
  1032. package/src/types/deployment.ts +39 -0
  1033. package/supabase/config.toml +357 -0
  1034. package/supabase/seed.sql +480 -0
  1035. package/tests/e2e/.gitkeep +0 -0
  1036. package/tests/e2e/QUICK_START.md +98 -0
  1037. package/tests/e2e/README.md +301 -0
  1038. package/tests/e2e/auth.spec.ts +583 -0
  1039. package/tests/e2e/global-setup.ts +23 -0
  1040. package/tests/e2e/global-teardown.ts +23 -0
  1041. package/tests/e2e/helpers/auth-helpers.ts +310 -0
  1042. package/tests/e2e/helpers/test-fixtures.ts +286 -0
  1043. package/tests/e2e/smoke-test.spec.ts +330 -0
  1044. package/tsconfig.json +48 -0
  1045. package/vitest.config.ts +50 -0
  1046. package/vitest.setup.ts +56 -0
  1047. package/dist/index.js +0 -778
@@ -0,0 +1,916 @@
1
+ /**
2
+ * useAnnotationIntegration Hook
3
+ *
4
+ * Core integration hook that connects frontend annotation state management
5
+ * with the real API endpoints. Provides optimistic updates with rollback,
6
+ * React Query caching, and integration with useAnnotationsWithHistory.
7
+ *
8
+ * Requirements: 1.4, 1.5, 4.5, 5.1
9
+ *
10
+ * @module features/annotations/hooks/use-annotation-integration
11
+ */
12
+
13
+ 'use client';
14
+
15
+ import { useCallback, useEffect, useMemo, useRef } from 'react';
16
+ import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
17
+ import { toast } from 'sonner';
18
+ import type { AnnotationShape, AttachmentAnnotation, AnnotationToolId, AnnotationHistoryEntry, UnsavedAnnotationState } from '../types';
19
+ import { useAnnotationsWithHistory } from './use-annotations-with-history';
20
+ import { useAnnotationTools } from './use-annotation-tools';
21
+ import {
22
+ getAnnotations,
23
+ createAnnotation as apiCreateAnnotation,
24
+ updateAnnotation as apiUpdateAnnotation,
25
+ deleteAnnotation as apiDeleteAnnotation,
26
+ transformToAttachmentAnnotation,
27
+ type AnnotationWithAuthor,
28
+ } from '../api/annotations-api';
29
+
30
+ // ============================================================================
31
+ // QUERY KEYS
32
+ // ============================================================================
33
+
34
+ export const annotationKeys = {
35
+ all: ['annotations'] as const,
36
+ lists: () => [...annotationKeys.all, 'list'] as const,
37
+ list: (issueId: string, attachmentId: string) =>
38
+ [...annotationKeys.lists(), { issueId, attachmentId }] as const,
39
+ detail: (annotationId: string) =>
40
+ [...annotationKeys.all, 'detail', annotationId] as const,
41
+ comments: (annotationId: string) =>
42
+ [...annotationKeys.detail(annotationId), 'comments'] as const,
43
+ };
44
+
45
+ // ============================================================================
46
+ // TYPES
47
+ // ============================================================================
48
+
49
+ export interface UseAnnotationIntegrationOptions {
50
+ issueId: string;
51
+ attachmentId: string;
52
+ enabled?: boolean;
53
+ onError?: (error: Error) => void;
54
+ }
55
+
56
+ export interface UseAnnotationIntegrationResult {
57
+ // State
58
+ annotations: AttachmentAnnotation[];
59
+ isLoading: boolean;
60
+ isFetching: boolean;
61
+ isError: boolean;
62
+ isSaving: boolean;
63
+
64
+ // Tool state (from useAnnotationTools)
65
+ activeTool: AnnotationToolId;
66
+ editModeEnabled: boolean;
67
+ canUndo: boolean;
68
+ canRedo: boolean;
69
+ handToolActive: boolean;
70
+ showShortcutsHelp: boolean;
71
+
72
+ // Actions
73
+ createAnnotation: (shape: AnnotationShape, description?: string) => Promise<void>;
74
+ updateAnnotation: (annotationId: string, shape: AnnotationShape, description?: string) => Promise<void>;
75
+ deleteAnnotation: (annotationId: string) => Promise<void>;
76
+ handleAnnotationMove: (annotationId: string, position: { x: number; y: number }) => void;
77
+ handleBoxAnnotationMove: (annotationId: string, start: { x: number; y: number }, end: { x: number; y: number }) => void;
78
+
79
+ // Tool controls
80
+ selectTool: (tool: AnnotationToolId) => void;
81
+ toggleEditMode: (next?: boolean) => void;
82
+ undo: () => void;
83
+ redo: () => void;
84
+ setShowShortcutsHelp: (show: boolean) => void;
85
+
86
+ // Drag state (prevents sync during drag) - per-annotation tracking
87
+ setDragging: (annotationId: string, isDragging: boolean) => void;
88
+
89
+ // Unsaved changes tracking
90
+ hasUnsavedChanges: () => boolean;
91
+
92
+ // Refresh
93
+ refetch: () => void;
94
+ }
95
+
96
+ // ============================================================================
97
+ // DEBOUNCE HELPER WITH FLUSH/CANCEL
98
+ // ============================================================================
99
+
100
+ interface DebouncedCallback<T extends (...args: any[]) => void> {
101
+ /** Call the debounced function */
102
+ (...args: Parameters<T>): void;
103
+ /** Immediately execute with the last pending args (if any) */
104
+ flush: () => void;
105
+ /** Cancel any pending execution */
106
+ cancel: () => void;
107
+ /** Check if there's a pending execution */
108
+ isPending: () => boolean;
109
+ }
110
+
111
+ function useDebouncedCallback<T extends (...args: any[]) => void>(
112
+ callback: T,
113
+ delay: number
114
+ ): DebouncedCallback<T> {
115
+ const timeoutRef = useRef<NodeJS.Timeout | null>(null);
116
+ const callbackRef = useRef(callback);
117
+ const pendingArgsRef = useRef<Parameters<T> | null>(null);
118
+
119
+ // Keep callback ref updated
120
+ callbackRef.current = callback;
121
+
122
+ const cancel = useCallback(() => {
123
+ if (timeoutRef.current) {
124
+ clearTimeout(timeoutRef.current);
125
+ timeoutRef.current = null;
126
+ }
127
+ pendingArgsRef.current = null;
128
+ }, []);
129
+
130
+ const flush = useCallback(() => {
131
+ if (timeoutRef.current && pendingArgsRef.current) {
132
+ clearTimeout(timeoutRef.current);
133
+ timeoutRef.current = null;
134
+ const args = pendingArgsRef.current;
135
+ pendingArgsRef.current = null;
136
+ callbackRef.current(...args);
137
+ }
138
+ }, []);
139
+
140
+ const isPending = useCallback(() => {
141
+ return timeoutRef.current !== null;
142
+ }, []);
143
+
144
+ const debouncedFn = useCallback(
145
+ ((...args: Parameters<T>) => {
146
+ // Store args for potential flush
147
+ pendingArgsRef.current = args;
148
+
149
+ if (timeoutRef.current) {
150
+ clearTimeout(timeoutRef.current);
151
+ }
152
+ timeoutRef.current = setTimeout(() => {
153
+ timeoutRef.current = null;
154
+ pendingArgsRef.current = null;
155
+ callbackRef.current(...args);
156
+ }, delay);
157
+ }),
158
+ [delay]
159
+ );
160
+
161
+ // Cleanup on unmount
162
+ useEffect(() => {
163
+ return () => {
164
+ if (timeoutRef.current) {
165
+ clearTimeout(timeoutRef.current);
166
+ }
167
+ };
168
+ }, []);
169
+
170
+ // Create stable result object with methods attached using Object.assign
171
+ // This creates a new function object rather than mutating the original
172
+ return useMemo(
173
+ () => Object.assign(
174
+ (...args: Parameters<T>) => debouncedFn(...args),
175
+ { flush, cancel, isPending }
176
+ ) as DebouncedCallback<T>,
177
+ [debouncedFn, flush, cancel, isPending]
178
+ );
179
+ }
180
+
181
+ // ============================================================================
182
+ // HOOK
183
+ // ============================================================================
184
+
185
+ /**
186
+ * Core integration hook for annotation management.
187
+ *
188
+ * Combines:
189
+ * - React Query for data fetching and caching
190
+ * - useAnnotationsWithHistory for local state with undo/redo
191
+ * - useAnnotationTools for tool state and keyboard shortcuts
192
+ * - Optimistic updates with rollback on failure
193
+ * - Debounced position updates
194
+ *
195
+ * @example
196
+ * ```tsx
197
+ * const {
198
+ * annotations,
199
+ * isLoading,
200
+ * activeTool,
201
+ * editModeEnabled,
202
+ * createAnnotation,
203
+ * selectTool,
204
+ * undo,
205
+ * redo,
206
+ * } = useAnnotationIntegration({
207
+ * issueId: 'issue_1',
208
+ * attachmentId: 'attach_1',
209
+ * });
210
+ * ```
211
+ */
212
+ export function useAnnotationIntegration(
213
+ options: UseAnnotationIntegrationOptions
214
+ ): UseAnnotationIntegrationResult {
215
+ const { issueId, attachmentId, enabled = true, onError } = options;
216
+ const queryClient = useQueryClient();
217
+
218
+ // Track current selected annotation for keyboard shortcuts
219
+ const selectedAnnotationRef = useRef<string | null>(null);
220
+
221
+ // ============================================================================
222
+ // LOCAL-FIRST AUTOSAVE STATE - Unified State Machine (Phase 1)
223
+ // ============================================================================
224
+
225
+ /**
226
+ * Unified annotation save state machine.
227
+ * Each annotation can be in one of these states:
228
+ * - 'idle': No local changes, synced with server
229
+ * - 'dragging': User is actively dragging
230
+ * - 'debouncing': Drag ended, waiting for debounce timer
231
+ * - 'saving': API call in flight
232
+ */
233
+ type AnnotationSaveState = 'idle' | 'dragging' | 'debouncing' | 'saving';
234
+
235
+ interface AnnotationStateEntry {
236
+ state: AnnotationSaveState;
237
+ clientRevision: number;
238
+ /** Timestamp when debounce grace period ends (only used in 'debouncing' state) */
239
+ graceEndTime?: number;
240
+ }
241
+
242
+ // Unified state tracking per annotation
243
+ const annotationStateRef = useRef(new Map<string, AnnotationStateEntry>());
244
+
245
+ // Track client revision per annotation (monotonically increasing on each commit)
246
+ const clientRevisionByIdRef = useRef(new Map<string, number>());
247
+
248
+ // Track pending saves per annotation (id -> latest pending clientRevision)
249
+ // Kept separate for revision matching in onSettled
250
+ const pendingRevisionByIdRef = useRef(new Map<string, number>());
251
+
252
+ // Track unsaved annotations for UI indication
253
+ const unsavedAnnotationsRef = useRef(new Map<string, UnsavedAnnotationState>());
254
+
255
+ // Grace period duration after drag ends
256
+ const GRACE_PERIOD_MS = 600; // Slightly longer than debounce (500ms)
257
+
258
+ // ============================================================================
259
+ // STATE MACHINE HELPERS
260
+ // ============================================================================
261
+
262
+ /** Get annotation state (defaults to 'idle') */
263
+ const getAnnotationState = useCallback((annotationId: string): AnnotationSaveState => {
264
+ return annotationStateRef.current.get(annotationId)?.state ?? 'idle';
265
+ }, []);
266
+
267
+ /** Set annotation to dragging state */
268
+ const setStateDragging = useCallback((annotationId: string) => {
269
+ const current = annotationStateRef.current.get(annotationId);
270
+ annotationStateRef.current.set(annotationId, {
271
+ state: 'dragging',
272
+ clientRevision: current?.clientRevision ?? 0,
273
+ });
274
+ }, []);
275
+
276
+ /** Set annotation to debouncing state with grace period */
277
+ const setStateDebouncing = useCallback((annotationId: string) => {
278
+ const current = annotationStateRef.current.get(annotationId);
279
+ annotationStateRef.current.set(annotationId, {
280
+ state: 'debouncing',
281
+ clientRevision: current?.clientRevision ?? 0,
282
+ graceEndTime: Date.now() + GRACE_PERIOD_MS,
283
+ });
284
+ }, []);
285
+
286
+ /** Set annotation to saving state */
287
+ const setStateSaving = useCallback((annotationId: string, clientRevision: number) => {
288
+ annotationStateRef.current.set(annotationId, {
289
+ state: 'saving',
290
+ clientRevision,
291
+ });
292
+ pendingRevisionByIdRef.current.set(annotationId, clientRevision);
293
+ }, []);
294
+
295
+ /** Set annotation to idle state (clear all tracking) */
296
+ const setStateIdle = useCallback((annotationId: string) => {
297
+ annotationStateRef.current.delete(annotationId);
298
+ pendingRevisionByIdRef.current.delete(annotationId);
299
+ }, []);
300
+
301
+ // ============================================================================
302
+ // SYNC BLOCKING HELPERS (Pure functions - no mutations)
303
+ // ============================================================================
304
+
305
+ /** Check if sync should be blocked globally */
306
+ const isSyncBlocked = useCallback(() => {
307
+ const now = Date.now();
308
+ for (const [, entry] of annotationStateRef.current) {
309
+ if (entry.state === 'dragging') return true;
310
+ if (entry.state === 'saving') return true;
311
+ if (entry.state === 'debouncing') {
312
+ // Only block if still within grace period
313
+ if (entry.graceEndTime && now < entry.graceEndTime) return true;
314
+ }
315
+ }
316
+ // Also check pending revisions (for stale saves)
317
+ if (pendingRevisionByIdRef.current.size > 0) return true;
318
+ return false;
319
+ }, []);
320
+
321
+ /** Check if sync should be blocked for a specific annotation */
322
+ const isSyncBlockedForAnnotation = useCallback((annotationId: string) => {
323
+ const entry = annotationStateRef.current.get(annotationId);
324
+ if (!entry) return false;
325
+
326
+ if (entry.state === 'dragging') return true;
327
+ if (entry.state === 'saving') return true;
328
+ if (entry.state === 'debouncing') {
329
+ // Only block if still within grace period
330
+ if (entry.graceEndTime && Date.now() < entry.graceEndTime) return true;
331
+ }
332
+
333
+ // Also check pending revision
334
+ if (pendingRevisionByIdRef.current.has(annotationId)) return true;
335
+
336
+ return false;
337
+ }, []);
338
+
339
+ // ============================================================================
340
+ // GRACE PERIOD CLEANUP (Phase 5 - Separate effect, no mutation in callbacks)
341
+ // ============================================================================
342
+
343
+ useEffect(() => {
344
+ const cleanupInterval = setInterval(() => {
345
+ const now = Date.now();
346
+ for (const [id, entry] of annotationStateRef.current) {
347
+ // Clean up expired debouncing states that haven't transitioned to saving
348
+ if (entry.state === 'debouncing' && entry.graceEndTime && now >= entry.graceEndTime) {
349
+ // If there's no pending save, the debounce was probably cancelled - go to idle
350
+ if (!pendingRevisionByIdRef.current.has(id)) {
351
+ annotationStateRef.current.delete(id);
352
+ }
353
+ }
354
+ }
355
+ }, 200); // Run every 200ms
356
+
357
+ return () => clearInterval(cleanupInterval);
358
+ }, []);
359
+
360
+ // Helper: Hard error codes that should trigger rollback
361
+ const HARD_ERROR_CODES = [403, 404, 402, 422];
362
+
363
+ const isHardSaveError = useCallback((error: unknown): boolean => {
364
+ if (error instanceof Error && 'status' in error) {
365
+ return HARD_ERROR_CODES.includes((error as { status: number }).status);
366
+ }
367
+ return false;
368
+ }, []);
369
+
370
+ // Helper: Mark annotation as unsaved
371
+ const markAnnotationUnsaved = useCallback((annotationId: string, opts?: { error?: Error }) => {
372
+ const current = unsavedAnnotationsRef.current.get(annotationId);
373
+ unsavedAnnotationsRef.current.set(annotationId, {
374
+ error: opts?.error,
375
+ retryCount: (current?.retryCount ?? 0) + 1,
376
+ });
377
+ }, []);
378
+
379
+ // Helper: Clear unsaved state for annotation
380
+ const clearAnnotationUnsaved = useCallback((annotationId: string) => {
381
+ unsavedAnnotationsRef.current.delete(annotationId);
382
+ }, []);
383
+
384
+ // Helper: Check if any annotations have unsaved changes
385
+ const hasUnsavedChanges = useCallback((): boolean => {
386
+ return unsavedAnnotationsRef.current.size > 0 || pendingRevisionByIdRef.current.size > 0;
387
+ }, []);
388
+
389
+ // ============================================================================
390
+ // QUERY - Fetch annotations
391
+ // ============================================================================
392
+
393
+ const queryKey = annotationKeys.list(issueId, attachmentId);
394
+
395
+ const query = useQuery({
396
+ queryKey,
397
+ queryFn: async () => {
398
+ const response = await getAnnotations(issueId, attachmentId);
399
+ return response.annotations.map(transformToAttachmentAnnotation);
400
+ },
401
+ enabled: enabled && !!issueId && !!attachmentId,
402
+ staleTime: 30 * 1000, // 30 seconds
403
+ retry: 1,
404
+ });
405
+
406
+ // ============================================================================
407
+ // LOCAL STATE - History-enabled annotation management
408
+ // ============================================================================
409
+
410
+ // Ref to hold pushHistory callback - allows connecting hooks without circular dependency
411
+ const pushHistoryRef = useRef<((entry: AnnotationHistoryEntry) => void) | null>(null);
412
+
413
+ // Ref to hold debouncedUpdate callback - allows undo/redo to trigger saves and flush on unload
414
+ const debouncedUpdateRef = useRef<DebouncedCallback<(annotationId: string, shape: AnnotationShape, clientRevision: number) => void> | null>(null);
415
+
416
+ const {
417
+ annotations: localAnnotations,
418
+ setAnnotations,
419
+ handleAnnotationMove: localHandleMove,
420
+ handleBoxAnnotationMove: localHandleBoxMove,
421
+ handleAnnotationCreate: localHandleCreate,
422
+ handleAnnotationDelete: localHandleDelete,
423
+ applyUndo,
424
+ applyRedo,
425
+ } = useAnnotationsWithHistory({
426
+ initialAnnotations: query.data ?? [],
427
+ onPushHistory: (entry) => pushHistoryRef.current?.(entry),
428
+ });
429
+
430
+ // Create lookup map for O(1) server annotation access (Phase 4 optimization)
431
+ const serverAnnotationsMap = useMemo(() => {
432
+ if (!query.data) return new Map<string, AttachmentAnnotation>();
433
+ return new Map(query.data.map(ann => [ann.id, ann]));
434
+ }, [query.data]);
435
+
436
+ // Sync local state when query data changes (blocked during drag or pending saves)
437
+ // Uses per-annotation filtering to allow unblocked annotations to sync
438
+ // NOTE: Using useEffect (not useMemo) for side effects - React 19 compatible
439
+ useEffect(() => {
440
+ if (!query.data || query.isFetching) return;
441
+
442
+ // If nothing is blocked, sync everything
443
+ if (!isSyncBlocked()) {
444
+ setAnnotations(query.data);
445
+ return;
446
+ }
447
+
448
+ // Otherwise, selectively sync only unblocked annotations
449
+ // This preserves local state for annotations being modified while
450
+ // still syncing updates for other annotations
451
+ const mergedAnnotations = localAnnotations.map((localAnn) => {
452
+ // If this annotation is blocked, keep local state
453
+ if (isSyncBlockedForAnnotation(localAnn.id)) {
454
+ return localAnn;
455
+ }
456
+
457
+ // O(1) lookup using Map instead of O(n) find
458
+ const serverAnn = serverAnnotationsMap.get(localAnn.id);
459
+ return serverAnn ?? localAnn;
460
+ });
461
+
462
+ // Only update if there are actual changes to unblocked annotations
463
+ const hasChanges = mergedAnnotations.some((merged, idx) => {
464
+ const local = localAnnotations[idx];
465
+ return merged.id !== local?.id || merged.x !== local?.x || merged.y !== local?.y;
466
+ });
467
+
468
+ if (hasChanges) {
469
+ setAnnotations(mergedAnnotations);
470
+ }
471
+ }, [query.data, query.isFetching, setAnnotations, isSyncBlocked, isSyncBlockedForAnnotation, localAnnotations, serverAnnotationsMap]);
472
+
473
+ // ============================================================================
474
+ // BEFOREUNLOAD HANDLER - Flush pending saves on tab close
475
+ // ============================================================================
476
+
477
+ useEffect(() => {
478
+ const handleBeforeUnload = (e: BeforeUnloadEvent) => {
479
+ // Flush any pending debounced saves immediately
480
+ if (debouncedUpdateRef.current?.isPending()) {
481
+ debouncedUpdateRef.current.flush();
482
+ }
483
+
484
+ // Show warning if there are still pending saves (after flush)
485
+ if (pendingRevisionByIdRef.current.size > 0 || annotationStateRef.current.size > 0) {
486
+ e.preventDefault();
487
+ e.returnValue = 'You have unsaved annotation changes.';
488
+ }
489
+ };
490
+ window.addEventListener('beforeunload', handleBeforeUnload);
491
+ return () => window.removeEventListener('beforeunload', handleBeforeUnload);
492
+ }, []);
493
+
494
+ // ============================================================================
495
+ // TOOL STATE
496
+ // ============================================================================
497
+
498
+ // Enhanced applyUndo that updates both local state AND React Query cache AND saves to server
499
+ const enhancedApplyUndo = useCallback((entry: AnnotationHistoryEntry) => {
500
+ // Apply to local state
501
+ applyUndo(entry);
502
+
503
+ // Update React Query cache and sync to server based on action type
504
+ if (entry.action === 'create') {
505
+ // Undo create: remove from cache and delete from server
506
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
507
+ old?.filter(ann => ann.id !== entry.annotationId) ?? []
508
+ );
509
+ // Delete from server (fire and forget - no toast needed as this is undo)
510
+ void apiDeleteAnnotation(issueId, attachmentId, entry.annotationId).catch(() => {
511
+ // Silently ignore errors - annotation might already be gone
512
+ });
513
+ } else if (entry.action === 'delete' && entry.fullAnnotation) {
514
+ // Undo delete: add back to cache and re-create on server
515
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
516
+ [...(old || []), entry.fullAnnotation!]
517
+ );
518
+ // Re-create on server with stored data
519
+ if (entry.fullAnnotation.shape) {
520
+ void apiCreateAnnotation(issueId, attachmentId, {
521
+ shape: entry.fullAnnotation.shape,
522
+ description: entry.fullAnnotation.description,
523
+ }).catch((err) => {
524
+ console.error('Failed to restore annotation on undo delete:', err);
525
+ });
526
+ }
527
+ } else if (entry.previousSnapshot && (entry.action === 'move' || entry.action === 'resize')) {
528
+ const prevShape = entry.previousSnapshot.shape;
529
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) => {
530
+ if (!old) return old;
531
+ return old.map(ann => {
532
+ if (ann.id !== entry.annotationId) return ann;
533
+ const updatedAnn = { ...ann, shape: prevShape };
534
+ if (prevShape.type === 'pin') {
535
+ updatedAnn.x = prevShape.position.x;
536
+ updatedAnn.y = prevShape.position.y;
537
+ } else if (prevShape.type === 'box') {
538
+ updatedAnn.x = (prevShape.start.x + prevShape.end.x) / 2;
539
+ updatedAnn.y = (prevShape.start.y + prevShape.end.y) / 2;
540
+ }
541
+ return updatedAnn;
542
+ });
543
+ });
544
+
545
+ // Queue a save for the undone position
546
+ const nextClientRevision = (clientRevisionByIdRef.current.get(entry.annotationId) ?? 0) + 1;
547
+ clientRevisionByIdRef.current.set(entry.annotationId, nextClientRevision);
548
+ pendingRevisionByIdRef.current.set(entry.annotationId, nextClientRevision);
549
+ debouncedUpdateRef.current?.(entry.annotationId, prevShape, nextClientRevision);
550
+ }
551
+ }, [applyUndo, queryClient, queryKey, issueId, attachmentId]);
552
+
553
+ // Enhanced applyRedo that updates both local state AND React Query cache AND saves to server
554
+ const enhancedApplyRedo = useCallback((entry: AnnotationHistoryEntry) => {
555
+ // Apply to local state
556
+ applyRedo(entry);
557
+
558
+ // Update React Query cache and sync to server based on action type
559
+ if (entry.action === 'create' && entry.fullAnnotation) {
560
+ // Redo create: add back to cache and re-create on server
561
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
562
+ [...(old || []), entry.fullAnnotation!]
563
+ );
564
+ // Re-create on server
565
+ if (entry.fullAnnotation.shape) {
566
+ void apiCreateAnnotation(issueId, attachmentId, {
567
+ shape: entry.fullAnnotation.shape,
568
+ description: entry.fullAnnotation.description,
569
+ }).catch((err) => {
570
+ console.error('Failed to re-create annotation on redo:', err);
571
+ });
572
+ }
573
+ } else if (entry.action === 'delete') {
574
+ // Redo delete: remove from cache and delete from server
575
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
576
+ old?.filter(ann => ann.id !== entry.annotationId) ?? []
577
+ );
578
+ // Delete from server
579
+ void apiDeleteAnnotation(issueId, attachmentId, entry.annotationId).catch(() => {
580
+ // Silently ignore errors - annotation might already be gone
581
+ });
582
+ } else if (entry.snapshot && (entry.action === 'move' || entry.action === 'resize')) {
583
+ const newShape = entry.snapshot.shape;
584
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) => {
585
+ if (!old) return old;
586
+ return old.map(ann => {
587
+ if (ann.id !== entry.annotationId) return ann;
588
+ const updatedAnn = { ...ann, shape: newShape };
589
+ if (newShape.type === 'pin') {
590
+ updatedAnn.x = newShape.position.x;
591
+ updatedAnn.y = newShape.position.y;
592
+ } else if (newShape.type === 'box') {
593
+ updatedAnn.x = (newShape.start.x + newShape.end.x) / 2;
594
+ updatedAnn.y = (newShape.start.y + newShape.end.y) / 2;
595
+ }
596
+ return updatedAnn;
597
+ });
598
+ });
599
+
600
+ // Queue a save for the redone position
601
+ const nextClientRevision = (clientRevisionByIdRef.current.get(entry.annotationId) ?? 0) + 1;
602
+ clientRevisionByIdRef.current.set(entry.annotationId, nextClientRevision);
603
+ pendingRevisionByIdRef.current.set(entry.annotationId, nextClientRevision);
604
+ debouncedUpdateRef.current?.(entry.annotationId, newShape, nextClientRevision);
605
+ }
606
+ }, [applyRedo, queryClient, queryKey, issueId, attachmentId]);
607
+
608
+ const tools = useAnnotationTools({
609
+ initialEditMode: false,
610
+ enableKeyboardShortcuts: true,
611
+ activeAnnotationId: selectedAnnotationRef.current,
612
+ onUndo: enhancedApplyUndo,
613
+ onRedo: enhancedApplyRedo,
614
+ onDelete: (annotationId: string) => {
615
+ void deleteMutation.mutateAsync(annotationId);
616
+ },
617
+ });
618
+
619
+ // Connect pushHistory to ref after tools is initialized
620
+ pushHistoryRef.current = tools.pushHistory;
621
+
622
+ // ============================================================================
623
+ // MUTATIONS - Create
624
+ // ============================================================================
625
+
626
+ const createMutation = useMutation({
627
+ mutationFn: async ({ shape, description }: { shape: AnnotationShape; description?: string }) => {
628
+ const response = await apiCreateAnnotation(issueId, attachmentId, { shape, description });
629
+ return transformToAttachmentAnnotation(response.annotation);
630
+ },
631
+ onSuccess: (newAnnotation) => {
632
+ // Update cache with new annotation
633
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
634
+ old ? [...old, newAnnotation] : [newAnnotation]
635
+ );
636
+ localHandleCreate(newAnnotation);
637
+ toast.success('Annotation created');
638
+ },
639
+ onError: (error: Error) => {
640
+ toast.error('Failed to create annotation');
641
+ onError?.(error);
642
+ },
643
+ });
644
+
645
+ // ============================================================================
646
+ // MUTATIONS - Update (with auto-retry for transient errors)
647
+ // ============================================================================
648
+
649
+ const updateMutation = useMutation({
650
+ mutationFn: async ({
651
+ annotationId,
652
+ shape,
653
+ description,
654
+ clientRevision,
655
+ }: {
656
+ annotationId: string;
657
+ shape?: AnnotationShape;
658
+ description?: string;
659
+ clientRevision: number;
660
+ }) => {
661
+ const response = await apiUpdateAnnotation(issueId, attachmentId, annotationId, {
662
+ shape,
663
+ description,
664
+ });
665
+ return transformToAttachmentAnnotation(response.annotation);
666
+ },
667
+ // Auto-retry configuration (Phase 6)
668
+ retry: (failureCount, error) => {
669
+ // Don't retry hard errors (permission, not found, validation, quota)
670
+ if (isHardSaveError(error)) return false;
671
+ // Retry up to 3 times for transient errors (network issues, 5xx)
672
+ return failureCount < 3;
673
+ },
674
+ retryDelay: (attemptIndex) => {
675
+ // Exponential backoff: 1s, 2s, 4s (capped at 8s)
676
+ return Math.min(1000 * Math.pow(2, attemptIndex), 8000);
677
+ },
678
+ onMutate: async ({ annotationId, shape }) => {
679
+ // Cancel outgoing refetches
680
+ await queryClient.cancelQueries({ queryKey });
681
+
682
+ // Snapshot previous value for hard-error rollback
683
+ const previousAnnotations = queryClient.getQueryData<AttachmentAnnotation[]>(queryKey);
684
+
685
+ // Optimistically update cache
686
+ if (shape) {
687
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
688
+ old?.map((ann) =>
689
+ ann.id === annotationId
690
+ ? {
691
+ ...ann,
692
+ shape,
693
+ x: shape.type === 'pin' ? shape.position.x : (shape.start.x + shape.end.x) / 2,
694
+ y: shape.type === 'pin' ? shape.position.y : (shape.start.y + shape.end.y) / 2,
695
+ }
696
+ : ann
697
+ )
698
+ );
699
+ }
700
+
701
+ return { previousAnnotations };
702
+ },
703
+ onError: (error: Error, variables, context) => {
704
+ // Mark annotation as unsaved for UI indication
705
+ markAnnotationUnsaved(variables.annotationId, { error });
706
+
707
+ // Only rollback for hard errors (403/404/402/422)
708
+ // For transient errors (network/5xx), keep local state - retries are handled automatically
709
+ if (isHardSaveError(error) && context?.previousAnnotations) {
710
+ queryClient.setQueryData(queryKey, context.previousAnnotations);
711
+ setAnnotations(context.previousAnnotations);
712
+ toast.error('Save failed - changes reverted');
713
+ } else {
714
+ // This will only show after all retries exhausted
715
+ toast.error('Failed to save annotation - please check your connection');
716
+ }
717
+ onError?.(error);
718
+ },
719
+ onSettled: (_data, _error, variables) => {
720
+ // Clear pending only if this response matches the latest pending revision
721
+ const pending = pendingRevisionByIdRef.current.get(variables.annotationId);
722
+ if (pending === variables.clientRevision) {
723
+ pendingRevisionByIdRef.current.delete(variables.annotationId);
724
+ clearAnnotationUnsaved(variables.annotationId);
725
+ // Clear annotation state since save completed
726
+ setStateIdle(variables.annotationId);
727
+ }
728
+ // DON'T invalidate queries after position updates!
729
+ // The optimistic update (onMutate) already set the correct position in the cache.
730
+ // Refetching risks getting stale server data before the PATCH has propagated,
731
+ // which causes the position to flicker back to the old location.
732
+ },
733
+ });
734
+
735
+ // Debounced update for position changes during drag - includes clientRevision
736
+ const debouncedUpdate = useDebouncedCallback(
737
+ (annotationId: string, shape: AnnotationShape, clientRevision: number) => {
738
+ void updateMutation.mutateAsync({ annotationId, shape, clientRevision });
739
+ },
740
+ 500 // 500ms debounce
741
+ );
742
+
743
+ // Connect ref after debouncedUpdate is defined (allows undo/redo to trigger saves)
744
+ debouncedUpdateRef.current = debouncedUpdate;
745
+
746
+ // ============================================================================
747
+ // MUTATIONS - Delete
748
+ // ============================================================================
749
+
750
+ const deleteMutation = useMutation({
751
+ mutationFn: async (annotationId: string) => {
752
+ await apiDeleteAnnotation(issueId, attachmentId, annotationId);
753
+ return annotationId;
754
+ },
755
+ onMutate: async (annotationId) => {
756
+ await queryClient.cancelQueries({ queryKey });
757
+
758
+ const previousAnnotations = queryClient.getQueryData<AttachmentAnnotation[]>(queryKey);
759
+
760
+ // Optimistically remove
761
+ queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
762
+ old?.filter((ann) => ann.id !== annotationId)
763
+ );
764
+ localHandleDelete(annotationId);
765
+
766
+ return { previousAnnotations };
767
+ },
768
+ onSuccess: () => {
769
+ toast.success('Annotation deleted');
770
+ },
771
+ onError: (error: Error, _annotationId, context) => {
772
+ // Rollback on error
773
+ if (context?.previousAnnotations) {
774
+ queryClient.setQueryData(queryKey, context.previousAnnotations);
775
+ setAnnotations(context.previousAnnotations);
776
+ toast.error('Delete failed - annotation restored');
777
+ } else {
778
+ toast.error('Failed to delete annotation');
779
+ }
780
+ onError?.(error);
781
+ },
782
+ onSettled: () => {
783
+ void queryClient.invalidateQueries({ queryKey });
784
+ },
785
+ });
786
+
787
+ // ============================================================================
788
+ // ACTION HANDLERS
789
+ // ============================================================================
790
+
791
+ const createAnnotation = useCallback(
792
+ async (shape: AnnotationShape, description?: string) => {
793
+ await createMutation.mutateAsync({ shape, description });
794
+ },
795
+ [createMutation]
796
+ );
797
+
798
+ const updateAnnotation = useCallback(
799
+ async (annotationId: string, shape: AnnotationShape, description?: string) => {
800
+ // Use client revision tracking for manual updates as well
801
+ const nextClientRevision = (clientRevisionByIdRef.current.get(annotationId) ?? 0) + 1;
802
+ clientRevisionByIdRef.current.set(annotationId, nextClientRevision);
803
+ pendingRevisionByIdRef.current.set(annotationId, nextClientRevision);
804
+ await updateMutation.mutateAsync({ annotationId, shape, description, clientRevision: nextClientRevision });
805
+ },
806
+ [updateMutation]
807
+ );
808
+
809
+ const deleteAnnotation = useCallback(
810
+ async (annotationId: string) => {
811
+ await deleteMutation.mutateAsync(annotationId);
812
+ },
813
+ [deleteMutation]
814
+ );
815
+
816
+ // Handle pin annotation move with debounced API sync and clientRevision tracking
817
+ const handleAnnotationMove = useCallback(
818
+ (annotationId: string, position: { x: number; y: number }) => {
819
+ // Mark as having local changes and set debouncing state (prevents race condition)
820
+ setStateDebouncing(annotationId);
821
+
822
+ // Update local state immediately
823
+ localHandleMove(annotationId, position);
824
+
825
+ // Increment client revision and mark as saving
826
+ const nextClientRevision = (clientRevisionByIdRef.current.get(annotationId) ?? 0) + 1;
827
+ clientRevisionByIdRef.current.set(annotationId, nextClientRevision);
828
+ setStateSaving(annotationId, nextClientRevision);
829
+
830
+ // Debounced API update with clientRevision
831
+ const newShape: AnnotationShape = { type: 'pin', position };
832
+ debouncedUpdate(annotationId, newShape, nextClientRevision);
833
+ },
834
+ [localHandleMove, debouncedUpdate, setStateDebouncing, setStateSaving]
835
+ );
836
+
837
+ // Handle box annotation move/resize with debounced API sync and clientRevision tracking
838
+ const handleBoxAnnotationMove = useCallback(
839
+ (annotationId: string, start: { x: number; y: number }, end: { x: number; y: number }) => {
840
+ // Mark as having local changes and set debouncing state (prevents race condition)
841
+ setStateDebouncing(annotationId);
842
+
843
+ // Update local state immediately
844
+ localHandleBoxMove(annotationId, start, end);
845
+
846
+ // Increment client revision and mark as saving
847
+ const nextClientRevision = (clientRevisionByIdRef.current.get(annotationId) ?? 0) + 1;
848
+ clientRevisionByIdRef.current.set(annotationId, nextClientRevision);
849
+ setStateSaving(annotationId, nextClientRevision);
850
+
851
+ // Debounced API update with clientRevision
852
+ const newShape: AnnotationShape = { type: 'box', start, end };
853
+ debouncedUpdate(annotationId, newShape, nextClientRevision);
854
+ },
855
+ [localHandleBoxMove, debouncedUpdate, setStateDebouncing, setStateSaving]
856
+ );
857
+
858
+ const refetch = useCallback(() => {
859
+ void query.refetch();
860
+ }, [query]);
861
+
862
+ // ============================================================================
863
+ // RETURN
864
+ // ============================================================================
865
+
866
+ const isSaving =
867
+ createMutation.isPending || updateMutation.isPending || deleteMutation.isPending;
868
+
869
+ return {
870
+ // State
871
+ annotations: localAnnotations,
872
+ isLoading: query.isLoading,
873
+ isFetching: query.isFetching,
874
+ isError: query.isError,
875
+ isSaving,
876
+
877
+ // Tool state
878
+ activeTool: tools.activeTool,
879
+ editModeEnabled: tools.editModeEnabled,
880
+ canUndo: tools.canUndo,
881
+ canRedo: tools.canRedo,
882
+ handToolActive: tools.handToolActive,
883
+ showShortcutsHelp: tools.showShortcutsHelp,
884
+
885
+ // Actions
886
+ createAnnotation,
887
+ updateAnnotation,
888
+ deleteAnnotation,
889
+ handleAnnotationMove,
890
+ handleBoxAnnotationMove,
891
+
892
+ // Tool controls
893
+ selectTool: tools.selectTool,
894
+ toggleEditMode: tools.toggleEditMode,
895
+ undo: tools.undo,
896
+ redo: tools.redo,
897
+ setShowShortcutsHelp: tools.setShowShortcutsHelp,
898
+
899
+ // Drag state - per-annotation tracking via state machine
900
+ setDragging: (annotationId: string, isDragging: boolean) => {
901
+ if (isDragging) {
902
+ setStateDragging(annotationId);
903
+ } else {
904
+ // Transition to debouncing state with grace period
905
+ // This prevents sync from overwriting local state before save mutation starts
906
+ setStateDebouncing(annotationId);
907
+ }
908
+ },
909
+
910
+ // Unsaved changes tracking
911
+ hasUnsavedChanges,
912
+
913
+ // Refresh
914
+ refetch,
915
+ };
916
+ }