@ripla/godd-mcp 1.0.4-canary.9 → 1.0.4

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 (348) hide show
  1. package/README.md +89 -94
  2. package/dist/godd.cjs +349 -233
  3. package/dist/godd.js +2193 -838
  4. package/dist/index.js +152 -122
  5. package/{notes-api → notes/api}/.env.example +6 -1
  6. package/{notes-api → notes/api}/README.md +6 -5
  7. package/{notes-api → notes/api}/app/main.py +29 -2
  8. package/{notes-api → notes/api}/app/routers/auth.py +6 -1
  9. package/{notes-api → notes/api}/app/routers/issues.py +157 -1
  10. package/{notes-api → notes/api}/app/routers/pr.py +2 -2
  11. package/{notes-api → notes/api}/app/services/github_app_token.py +32 -1
  12. package/{notes-api → notes/api}/app/services/github_issues.py +101 -11
  13. package/{notes-api → notes/api}/app/services/github_pr.py +6 -1
  14. package/{notes-api → notes/api}/tests/test_github_app_token.py +49 -1
  15. package/{notes-api → notes/api}/tests/test_github_service.py +42 -0
  16. package/notes/api/tests/test_health.py +52 -0
  17. package/{notes-api → notes/api}/tests/test_issues.py +216 -0
  18. package/{notes-api → notes/api}/tests/test_pr.py +24 -0
  19. package/{notes-api → notes/api}/tests/test_pr_chain.py +19 -0
  20. package/{notes-api → notes/api}/uv.lock +178 -2
  21. package/notes/app/DESIGN.md +273 -0
  22. package/{notes-app → notes/app}/README.md +4 -1
  23. package/notes/app/docker/Dockerfile.e2e +19 -0
  24. package/notes/app/docker/Dockerfile.prod +15 -0
  25. package/notes/app/docker/Dockerfile.test +17 -0
  26. package/notes/app/e2e/file-tree.spec.ts +26 -0
  27. package/notes/app/e2e/file-view.spec.ts +25 -0
  28. package/notes/app/e2e/fixtures.ts +28 -0
  29. package/{notes-app → notes/app}/e2e/global-setup.ts +5 -7
  30. package/notes/app/e2e/login.spec.ts +25 -0
  31. package/notes/app/e2e/pages/login-page.ts +29 -0
  32. package/notes/app/e2e/pages/main-page.ts +29 -0
  33. package/notes/app/e2e/panel-resize-handle.spec.ts +213 -0
  34. package/{notes-app → notes/app}/eslint.config.js +9 -0
  35. package/{notes-app → notes/app}/package.json +0 -3
  36. package/{notes-app → notes/app}/pnpm-workspace.yaml +1 -0
  37. package/notes/app/src/components/CommentPanel.test.tsx +194 -0
  38. package/{notes-app → notes/app}/src/components/CommentPanel.tsx +119 -10
  39. package/notes/app/src/components/CommentSelectionPopover.tsx +26 -0
  40. package/notes/app/src/components/EditToolbar.test.tsx +102 -0
  41. package/{notes-app → notes/app}/src/components/EditToolbar.tsx +5 -54
  42. package/notes/app/src/components/FileInfoPanel.branch-tab.test.tsx +250 -0
  43. package/notes/app/src/components/FileInfoPanel.history-tab.test.tsx +146 -0
  44. package/notes/app/src/components/FileInfoPanel.test.tsx +101 -0
  45. package/{notes-app → notes/app}/src/components/FileInfoPanel.tsx +135 -20
  46. package/notes/app/src/components/IssueCommentThread.test.tsx +93 -0
  47. package/notes/app/src/components/IssueCommentThread.tsx +156 -0
  48. package/notes/app/src/components/IssueDetailView.test.tsx +1118 -0
  49. package/notes/app/src/components/IssueDetailView.tsx +476 -0
  50. package/notes/app/src/components/IssueList.test.tsx +545 -0
  51. package/notes/app/src/components/IssueList.tsx +374 -0
  52. package/{notes-app → notes/app}/src/components/LabelSelector.test.tsx +94 -8
  53. package/notes/app/src/components/LabelSelector.tsx +452 -0
  54. package/{notes-app → notes/app}/src/components/MarkdownEditor.tsx +140 -2
  55. package/notes/app/src/components/PanelResizeHandle.test.tsx +481 -0
  56. package/notes/app/src/components/PanelResizeHandle.tsx +96 -0
  57. package/{notes-app → notes/app}/src/hooks/useEditSession.test.ts +171 -23
  58. package/{notes-app → notes/app}/src/hooks/useEditSession.ts +49 -62
  59. package/notes/app/src/hooks/useMdUpLayout.test.ts +10 -0
  60. package/notes/app/src/hooks/useMdUpLayout.ts +27 -0
  61. package/notes/app/src/hooks/usePeriodicSync.test.ts +244 -0
  62. package/notes/app/src/hooks/usePeriodicSync.ts +83 -0
  63. package/notes/app/src/hooks/useResizablePanelWidth.test.ts +251 -0
  64. package/notes/app/src/hooks/useResizablePanelWidth.ts +167 -0
  65. package/{notes-app → notes/app}/src/lib/api.test.ts +81 -1
  66. package/{notes-app → notes/app}/src/lib/api.ts +98 -4
  67. package/notes/app/src/lib/commentAnchor.test.ts +22 -0
  68. package/notes/app/src/lib/commentAnchor.ts +28 -0
  69. package/notes/app/src/lib/commentHighlight.test.ts +110 -0
  70. package/notes/app/src/lib/commentHighlight.ts +44 -0
  71. package/notes/app/src/lib/github-sync.test.ts +126 -0
  72. package/notes/app/src/lib/github-sync.ts +89 -0
  73. package/notes/app/src/lib/issue-list-utils.test.ts +275 -0
  74. package/notes/app/src/lib/issue-list-utils.ts +135 -0
  75. package/notes/app/src/lib/issue-mobile-layout.test.ts +17 -0
  76. package/notes/app/src/lib/issue-mobile-layout.ts +20 -0
  77. package/notes/app/src/lib/issue-url-sync.test.ts +73 -0
  78. package/notes/app/src/lib/issue-url-sync.ts +43 -0
  79. package/notes/app/src/lib/main-page-default-view.test.ts +37 -0
  80. package/notes/app/src/lib/main-page-default-view.ts +15 -0
  81. package/notes/app/src/lib/resizable-panel.test.ts +199 -0
  82. package/notes/app/src/lib/resizable-panel.ts +136 -0
  83. package/notes/app/src/pages/LoginPage.test.tsx +17 -0
  84. package/notes/app/src/pages/MainPage.branch-modal.test.tsx +226 -0
  85. package/notes/app/src/pages/MainPage.issue-sync.test.tsx +145 -0
  86. package/notes/app/src/pages/MainPage.sidebar.test.tsx +361 -0
  87. package/{notes-app → notes/app}/src/pages/MainPage.tsx +406 -61
  88. package/{notes-app → notes/app}/src/pages/SettingsPage.test.tsx +32 -23
  89. package/{notes-app → notes/app}/src/pages/UserManagementPage.test.tsx +8 -18
  90. package/notes/app/src/test/mocks.ts +156 -0
  91. package/notes/app/src/test/render.tsx +27 -0
  92. package/package.json +3 -5
  93. package/notes-api/tests/test_health.py +0 -11
  94. package/notes-app/docker/Dockerfile.e2e +0 -14
  95. package/notes-app/docker/Dockerfile.prod +0 -12
  96. package/notes-app/docker/Dockerfile.test +0 -12
  97. package/notes-app/e2e/file-tree.spec.ts +0 -29
  98. package/notes-app/e2e/file-view.spec.ts +0 -24
  99. package/notes-app/e2e/login.spec.ts +0 -32
  100. package/notes-app/src/components/IssueDetailView.test.tsx +0 -201
  101. package/notes-app/src/components/IssueDetailView.tsx +0 -318
  102. package/notes-app/src/components/IssueList.test.tsx +0 -93
  103. package/notes-app/src/components/IssueList.tsx +0 -169
  104. package/notes-app/src/components/LabelSelector.tsx +0 -276
  105. package/notes-app/src/pages/LoginPage.test.tsx +0 -29
  106. package/stacks/django-vue.yaml +0 -33
  107. package/stacks/fastapi-react.yaml +0 -160
  108. package/stacks/flutter-firebase.yaml +0 -58
  109. package/stacks/nextjs-prisma.yaml +0 -33
  110. package/stacks/schema.json +0 -144
  111. package/templates/agents/architect.hbs +0 -183
  112. package/templates/agents/auto-documenter.hbs +0 -36
  113. package/templates/agents/backend-developer.hbs +0 -175
  114. package/templates/agents/code-reviewer.hbs +0 -29
  115. package/templates/agents/database-developer.hbs +0 -52
  116. package/templates/agents/debug-specialist.hbs +0 -39
  117. package/templates/agents/environment-setup.hbs +0 -41
  118. package/templates/agents/frontend-developer.hbs +0 -45
  119. package/templates/agents/integration-qa.hbs +0 -26
  120. package/templates/agents/performance-qa.hbs +0 -24
  121. package/templates/agents/pr-writer.hbs +0 -53
  122. package/templates/agents/quality-lead.hbs +0 -26
  123. package/templates/agents/requirements-analyst.hbs +0 -26
  124. package/templates/agents/security-analyst.hbs +0 -21
  125. package/templates/agents/security-reviewer.hbs +0 -20
  126. package/templates/agents/ssot-updater.hbs +0 -45
  127. package/templates/agents/technical-writer.hbs +0 -28
  128. package/templates/agents/unit-test-engineer.hbs +0 -19
  129. package/templates/agents/user-clone.hbs +0 -49
  130. package/templates/github-actions/aws/deploy-notes.yml.hbs +0 -117
  131. package/templates/github-actions/azure/deploy-notes.yml.hbs +0 -78
  132. package/templates/github-actions/gcp/deploy-notes.yml.hbs +0 -76
  133. package/templates/github-actions/vercel-railway/deploy-notes.yml.hbs +0 -49
  134. package/templates/mindsets/agent-stdio.hbs +0 -51
  135. package/templates/mindsets/dart.hbs +0 -39
  136. package/templates/mindsets/ddd-clean-architecture.hbs +0 -52
  137. package/templates/mindsets/electron.hbs +0 -41
  138. package/templates/mindsets/fastapi.hbs +0 -40
  139. package/templates/mindsets/flutter.hbs +0 -56
  140. package/templates/mindsets/kotlin.hbs +0 -46
  141. package/templates/mindsets/postgresql.hbs +0 -39
  142. package/templates/mindsets/python.hbs +0 -39
  143. package/templates/mindsets/react.hbs +0 -51
  144. package/templates/mindsets/swift.hbs +0 -46
  145. package/templates/mindsets/terraform.hbs +0 -53
  146. package/templates/mindsets/typescript.hbs +0 -34
  147. package/templates/mindsets/vite.hbs +0 -37
  148. package/templates/notes-compose.yml +0 -82
  149. package/templates/partials/cogito-geo.hbs +0 -84
  150. package/templates/partials/cogito-godd.hbs +0 -22
  151. package/templates/partials/cogito-grammar.hbs +0 -105
  152. package/templates/partials/cogito-particle.hbs +0 -76
  153. package/templates/partials/cogito-root-ai.hbs +0 -79
  154. package/templates/partials/cogito-root-arch.hbs +0 -75
  155. package/templates/partials/cogito-root-core.hbs +0 -75
  156. package/templates/partials/cogito-root-daily.hbs +0 -58
  157. package/templates/partials/cogito-root-dev.hbs +0 -71
  158. package/templates/partials/cogito-root-devops.hbs +0 -71
  159. package/templates/partials/cogito-root-framework.hbs +0 -80
  160. package/templates/partials/cogito-root-lang.hbs +0 -74
  161. package/templates/partials/godd-core.hbs +0 -50
  162. package/templates/partials/project-context.hbs +0 -52
  163. package/templates/partials/ssot-header.hbs +0 -40
  164. package/templates/partials/ssot-rules.hbs +0 -30
  165. package/templates/prompts/adr.hbs +0 -39
  166. package/templates/prompts/agent-dev-workflow.hbs +0 -34
  167. package/templates/prompts/analyze-report.hbs +0 -36
  168. package/templates/prompts/auto-documentation.hbs +0 -37
  169. package/templates/prompts/check.hbs +0 -52
  170. package/templates/prompts/commit.hbs +0 -96
  171. package/templates/prompts/dev.hbs +0 -127
  172. package/templates/prompts/docs-init.hbs +0 -101
  173. package/templates/prompts/docs-update.hbs +0 -51
  174. package/templates/prompts/git-workflow.hbs +0 -125
  175. package/templates/prompts/impact-analysis.hbs +0 -43
  176. package/templates/prompts/install.hbs +0 -40
  177. package/templates/prompts/new-branch.hbs +0 -61
  178. package/templates/prompts/notes-deploy.hbs +0 -138
  179. package/templates/prompts/pr-analyze.hbs +0 -44
  180. package/templates/prompts/pr-create.hbs +0 -125
  181. package/templates/prompts/push-execute.hbs +0 -83
  182. package/templates/prompts/push.hbs +0 -24
  183. package/templates/prompts/release-notes.hbs +0 -38
  184. package/templates/prompts/requirements-to-business-flow.hbs +0 -29
  185. package/templates/prompts/requirements-to-tickets.hbs +0 -26
  186. package/templates/prompts/review.hbs +0 -126
  187. package/templates/prompts/setup.hbs +0 -247
  188. package/templates/prompts/spec.hbs +0 -35
  189. package/templates/prompts/specification-to-tickets.hbs +0 -24
  190. package/templates/prompts/submit.hbs +0 -143
  191. package/templates/prompts/sync.hbs +0 -63
  192. package/templates/prompts/test-plan.hbs +0 -56
  193. package/templates/scripts/aws/terraform-backend-setup.sh.hbs +0 -149
  194. package/templates/terraform/aws/alb.tf.hbs +0 -58
  195. package/templates/terraform/aws/backend.tf.hbs +0 -10
  196. package/templates/terraform/aws/cloudfront.tf.hbs +0 -96
  197. package/templates/terraform/aws/ecr.tf.hbs +0 -30
  198. package/templates/terraform/aws/ecs.tf.hbs +0 -142
  199. package/templates/terraform/aws/iam.tf.hbs +0 -88
  200. package/templates/terraform/aws/local.tf.hbs +0 -34
  201. package/templates/terraform/aws/output.tf.hbs +0 -49
  202. package/templates/terraform/aws/provider.tf.hbs +0 -26
  203. package/templates/terraform/aws/rds.tf.hbs +0 -38
  204. package/templates/terraform/aws/s3.tf.hbs +0 -34
  205. package/templates/terraform/aws/secrets.tf.hbs +0 -53
  206. package/templates/terraform/aws/security_groups.tf.hbs +0 -81
  207. package/templates/terraform/aws/vpc.tf.hbs +0 -63
  208. package/templates/terraform/azure/backend.tf.hbs +0 -8
  209. package/templates/terraform/azure/iam.tf.hbs +0 -36
  210. package/templates/terraform/azure/main.tf.hbs +0 -203
  211. package/templates/terraform/azure/output.tf.hbs +0 -19
  212. package/templates/terraform/azure/provider.tf.hbs +0 -19
  213. package/templates/terraform/gcp/backend.tf.hbs +0 -6
  214. package/templates/terraform/gcp/iam.tf.hbs +0 -47
  215. package/templates/terraform/gcp/main.tf.hbs +0 -175
  216. package/templates/terraform/gcp/output.tf.hbs +0 -15
  217. package/templates/terraform/gcp/provider.tf.hbs +0 -19
  218. package/templates/terraform/vercel-railway/main.tf.hbs +0 -64
  219. package/templates/terraform/vercel-railway/output.tf.hbs +0 -11
  220. /package/{notes-api → notes/api}/.dockerignore +0 -0
  221. /package/{notes-api → notes/api}/alembic/README +0 -0
  222. /package/{notes-api → notes/api}/alembic/env.py +0 -0
  223. /package/{notes-api → notes/api}/alembic/script.py.mako +0 -0
  224. /package/{notes-api → notes/api}/alembic/versions/001_initial_notes_tables.py +0 -0
  225. /package/{notes-api → notes/api}/alembic/versions/002_add_performance_indexes.py +0 -0
  226. /package/{notes-api → notes/api}/alembic/versions/003_add_yjs_documents.py +0 -0
  227. /package/{notes-api → notes/api}/alembic/versions/004_add_comments.py +0 -0
  228. /package/{notes-api → notes/api}/alembic/versions/005_add_github_username.py +0 -0
  229. /package/{notes-api → notes/api}/alembic/versions/006_add_check_constraints.py +0 -0
  230. /package/{notes-api → notes/api}/alembic.ini +0 -0
  231. /package/{notes-api → notes/api}/app/__init__.py +0 -0
  232. /package/{notes-api → notes/api}/app/config.py +0 -0
  233. /package/{notes-api → notes/api}/app/database.py +0 -0
  234. /package/{notes-api → notes/api}/app/http_client.py +0 -0
  235. /package/{notes-api → notes/api}/app/models/__init__.py +0 -0
  236. /package/{notes-api → notes/api}/app/models/audit.py +0 -0
  237. /package/{notes-api → notes/api}/app/models/base.py +0 -0
  238. /package/{notes-api → notes/api}/app/models/collab.py +0 -0
  239. /package/{notes-api → notes/api}/app/models/comment.py +0 -0
  240. /package/{notes-api → notes/api}/app/models/session.py +0 -0
  241. /package/{notes-api → notes/api}/app/models/setting.py +0 -0
  242. /package/{notes-api → notes/api}/app/models/user.py +0 -0
  243. /package/{notes-api → notes/api}/app/path_validation.py +0 -0
  244. /package/{notes-api → notes/api}/app/routers/__init__.py +0 -0
  245. /package/{notes-api → notes/api}/app/routers/collab.py +0 -0
  246. /package/{notes-api → notes/api}/app/routers/comments.py +0 -0
  247. /package/{notes-api → notes/api}/app/routers/files.py +0 -0
  248. /package/{notes-api → notes/api}/app/routers/settings.py +0 -0
  249. /package/{notes-api → notes/api}/app/routers/tree.py +0 -0
  250. /package/{notes-api → notes/api}/app/routers/upload.py +0 -0
  251. /package/{notes-api → notes/api}/app/routers/users.py +0 -0
  252. /package/{notes-api → notes/api}/app/schemas/__init__.py +0 -0
  253. /package/{notes-api → notes/api}/app/schemas/auth.py +0 -0
  254. /package/{notes-api → notes/api}/app/schemas/user.py +0 -0
  255. /package/{notes-api → notes/api}/app/security.py +0 -0
  256. /package/{notes-api → notes/api}/app/services/__init__.py +0 -0
  257. /package/{notes-api → notes/api}/app/services/audit.py +0 -0
  258. /package/{notes-api → notes/api}/app/services/business_date.py +0 -0
  259. /package/{notes-api → notes/api}/app/services/collab.py +0 -0
  260. /package/{notes-api → notes/api}/app/services/github.py +0 -0
  261. /package/{notes-api → notes/api}/app/services/pr_chain.py +0 -0
  262. /package/{notes-api → notes/api}/app/startup.py +0 -0
  263. /package/{notes-api → notes/api}/docker/Dockerfile +0 -0
  264. /package/{notes-api → notes/api}/docker/Dockerfile.test +0 -0
  265. /package/{notes-api → notes/api}/pyproject.toml +0 -0
  266. /package/{notes-api → notes/api}/tests/conftest.py +0 -0
  267. /package/{notes-api → notes/api}/tests/test_audit.py +0 -0
  268. /package/{notes-api → notes/api}/tests/test_auth.py +0 -0
  269. /package/{notes-api → notes/api}/tests/test_business_date.py +0 -0
  270. /package/{notes-api → notes/api}/tests/test_collab.py +0 -0
  271. /package/{notes-api → notes/api}/tests/test_comments.py +0 -0
  272. /package/{notes-api → notes/api}/tests/test_config_ssl.py +0 -0
  273. /package/{notes-api → notes/api}/tests/test_config_warnings.py +0 -0
  274. /package/{notes-api → notes/api}/tests/test_files.py +0 -0
  275. /package/{notes-api → notes/api}/tests/test_path_validation.py +0 -0
  276. /package/{notes-api → notes/api}/tests/test_security.py +0 -0
  277. /package/{notes-api → notes/api}/tests/test_settings.py +0 -0
  278. /package/{notes-api → notes/api}/tests/test_tree.py +0 -0
  279. /package/{notes-api → notes/api}/tests/test_upload.py +0 -0
  280. /package/{notes-api → notes/api}/tests/test_users.py +0 -0
  281. /package/{notes-app → notes/app}/.dockerignore +0 -0
  282. /package/{notes-app → notes/app}/index.html +0 -0
  283. /package/{notes-app → notes/app}/nginx.conf +0 -0
  284. /package/{notes-app → notes/app}/package-lock.json +0 -0
  285. /package/{notes-app → notes/app}/playwright.config.ts +0 -0
  286. /package/{notes-app → notes/app}/pnpm-lock.yaml +0 -0
  287. /package/{notes-app → notes/app}/src/App.tsx +0 -0
  288. /package/{notes-app → notes/app}/src/components/BranchSelectorModal.tsx +0 -0
  289. /package/{notes-app → notes/app}/src/components/CodeBlockView.test.tsx +0 -0
  290. /package/{notes-app → notes/app}/src/components/CodeBlockView.tsx +0 -0
  291. /package/{notes-app → notes/app}/src/components/CollabStatus.tsx +0 -0
  292. /package/{notes-app → notes/app}/src/components/DiffPreview.tsx +0 -0
  293. /package/{notes-app → notes/app}/src/components/DrawioEditor.tsx +0 -0
  294. /package/{notes-app → notes/app}/src/components/DrawioViewer.tsx +0 -0
  295. /package/{notes-app → notes/app}/src/components/ErrorBoundary.test.tsx +0 -0
  296. /package/{notes-app → notes/app}/src/components/ErrorBoundary.tsx +0 -0
  297. /package/{notes-app → notes/app}/src/components/FileContentView.tsx +0 -0
  298. /package/{notes-app → notes/app}/src/components/FileContextMenu.test.tsx +0 -0
  299. /package/{notes-app → notes/app}/src/components/FileContextMenu.tsx +0 -0
  300. /package/{notes-app → notes/app}/src/components/FileDialogs.test.tsx +0 -0
  301. /package/{notes-app → notes/app}/src/components/FileDialogs.tsx +0 -0
  302. /package/{notes-app → notes/app}/src/components/SaveStatusIndicator.test.tsx +0 -0
  303. /package/{notes-app → notes/app}/src/components/SaveStatusIndicator.tsx +0 -0
  304. /package/{notes-app → notes/app}/src/components/Skeleton.tsx +0 -0
  305. /package/{notes-app → notes/app}/src/components/SpreadsheetEditor.test.tsx +0 -0
  306. /package/{notes-app → notes/app}/src/components/SpreadsheetEditor.tsx +0 -0
  307. /package/{notes-app → notes/app}/src/components/TextEditor.tsx +0 -0
  308. /package/{notes-app → notes/app}/src/components/ToastContainer.tsx +0 -0
  309. /package/{notes-app → notes/app}/src/components/TreeNode.tsx +0 -0
  310. /package/{notes-app → notes/app}/src/contexts/AuthContext.test.tsx +0 -0
  311. /package/{notes-app → notes/app}/src/contexts/AuthContext.tsx +0 -0
  312. /package/{notes-app → notes/app}/src/contexts/ToastContext.test.ts +0 -0
  313. /package/{notes-app → notes/app}/src/contexts/ToastContext.tsx +0 -0
  314. /package/{notes-app → notes/app}/src/hooks/useAutoSave.test.ts +0 -0
  315. /package/{notes-app → notes/app}/src/hooks/useAutoSave.ts +0 -0
  316. /package/{notes-app → notes/app}/src/hooks/useCollaboration.ts +0 -0
  317. /package/{notes-app → notes/app}/src/hooks/useComments.ts +0 -0
  318. /package/{notes-app → notes/app}/src/hooks/useYText.ts +0 -0
  319. /package/{notes-app → notes/app}/src/lib/branch-diff.test.ts +0 -0
  320. /package/{notes-app → notes/app}/src/lib/branch-diff.ts +0 -0
  321. /package/{notes-app → notes/app}/src/lib/created-file.test.ts +0 -0
  322. /package/{notes-app → notes/app}/src/lib/created-file.ts +0 -0
  323. /package/{notes-app → notes/app}/src/lib/csv-utils.test.ts +0 -0
  324. /package/{notes-app → notes/app}/src/lib/csv-utils.ts +0 -0
  325. /package/{notes-app → notes/app}/src/lib/deepLinkPathToSearch.test.ts +0 -0
  326. /package/{notes-app → notes/app}/src/lib/deepLinkPathToSearch.ts +0 -0
  327. /package/{notes-app → notes/app}/src/lib/draft-storage.test.ts +0 -0
  328. /package/{notes-app → notes/app}/src/lib/draft-storage.ts +0 -0
  329. /package/{notes-app → notes/app}/src/lib/mermaid-render.ts +0 -0
  330. /package/{notes-app → notes/app}/src/lib/postLoginRedirect.test.ts +0 -0
  331. /package/{notes-app → notes/app}/src/lib/postLoginRedirect.ts +0 -0
  332. /package/{notes-app → notes/app}/src/lib/tree-utils.ts +0 -0
  333. /package/{notes-app → notes/app}/src/lib/user-colors.ts +0 -0
  334. /package/{notes-app → notes/app}/src/lib/xlsx-utils.test.ts +0 -0
  335. /package/{notes-app → notes/app}/src/lib/xlsx-utils.ts +0 -0
  336. /package/{notes-app → notes/app}/src/main.tsx +0 -0
  337. /package/{notes-app → notes/app}/src/pages/LoginPage.tsx +0 -0
  338. /package/{notes-app → notes/app}/src/pages/MainPage.insertChildEntry.test.ts +0 -0
  339. /package/{notes-app → notes/app}/src/pages/SettingsPage.tsx +0 -0
  340. /package/{notes-app → notes/app}/src/pages/UserManagementPage.tsx +0 -0
  341. /package/{notes-app → notes/app}/src/styles/globals.css +0 -0
  342. /package/{notes-app → notes/app}/src/types.ts +0 -0
  343. /package/{notes-app → notes/app}/src/vite-env.d.ts +0 -0
  344. /package/{notes-app → notes/app}/tsconfig.json +0 -0
  345. /package/{notes-app → notes/app}/tsconfig.node.json +0 -0
  346. /package/{notes-app → notes/app}/vite.config.js +0 -0
  347. /package/{notes-app → notes/app}/vite.config.ts +0 -0
  348. /package/{notes-app → notes/app}/vitest.config.ts +0 -0
@@ -0,0 +1,244 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { renderHook } from "@testing-library/react";
3
+ import { usePeriodicSync } from "./usePeriodicSync";
4
+
5
+ describe("usePeriodicSync", () => {
6
+ beforeEach(() => {
7
+ vi.useFakeTimers();
8
+ Object.defineProperty(document, "visibilityState", {
9
+ configurable: true,
10
+ value: "visible",
11
+ });
12
+ });
13
+
14
+ afterEach(() => {
15
+ vi.useRealTimers();
16
+ });
17
+
18
+ it("enabled 時に interval ごとに onSync を呼ぶ", () => {
19
+ const onSync = vi.fn();
20
+ renderHook(() =>
21
+ usePeriodicSync({ enabled: true, intervalMs: 10_000, onSync }),
22
+ );
23
+
24
+ expect(onSync).toHaveBeenCalledTimes(1);
25
+ vi.advanceTimersByTime(10_000);
26
+ expect(onSync).toHaveBeenCalledTimes(2);
27
+ });
28
+
29
+ it("disabled 時は onSync を呼ばない", () => {
30
+ const onSync = vi.fn();
31
+ renderHook(() =>
32
+ usePeriodicSync({ enabled: false, intervalMs: 10_000, onSync }),
33
+ );
34
+
35
+ vi.advanceTimersByTime(30_000);
36
+ expect(onSync).not.toHaveBeenCalled();
37
+ });
38
+
39
+ it("タブ非表示中は interval でも onSync しない", () => {
40
+ const onSync = vi.fn();
41
+ renderHook(() =>
42
+ usePeriodicSync({ enabled: true, intervalMs: 10_000, onSync }),
43
+ );
44
+
45
+ onSync.mockClear();
46
+ Object.defineProperty(document, "visibilityState", {
47
+ configurable: true,
48
+ value: "hidden",
49
+ });
50
+ vi.advanceTimersByTime(10_000);
51
+ expect(onSync).not.toHaveBeenCalled();
52
+ });
53
+
54
+ it("タブ再表示時に onSync する", () => {
55
+ const onSync = vi.fn();
56
+ renderHook(() =>
57
+ usePeriodicSync({ enabled: true, intervalMs: 10_000, onSync }),
58
+ );
59
+
60
+ onSync.mockClear();
61
+ Object.defineProperty(document, "visibilityState", {
62
+ configurable: true,
63
+ value: "hidden",
64
+ });
65
+ document.dispatchEvent(new Event("visibilitychange"));
66
+ expect(onSync).not.toHaveBeenCalled();
67
+
68
+ Object.defineProperty(document, "visibilityState", {
69
+ configurable: true,
70
+ value: "visible",
71
+ });
72
+ document.dispatchEvent(new Event("visibilitychange"));
73
+ expect(onSync).toHaveBeenCalledTimes(1);
74
+ });
75
+
76
+ it("skipInitialSync 時は初回マウントの即時 sync を行わない", () => {
77
+ const onSync = vi.fn();
78
+ renderHook(() =>
79
+ usePeriodicSync({
80
+ enabled: true,
81
+ intervalMs: 10_000,
82
+ onSync,
83
+ skipInitialSync: true,
84
+ }),
85
+ );
86
+
87
+ expect(onSync).not.toHaveBeenCalled();
88
+ });
89
+
90
+ it("skipInitialSync でも interval では onSync する", () => {
91
+ const onSync = vi.fn();
92
+ renderHook(() =>
93
+ usePeriodicSync({
94
+ enabled: true,
95
+ intervalMs: 10_000,
96
+ onSync,
97
+ skipInitialSync: true,
98
+ }),
99
+ );
100
+
101
+ vi.advanceTimersByTime(10_000);
102
+ expect(onSync).toHaveBeenCalledTimes(1);
103
+ });
104
+
105
+ it("skipInitialSync でもタブ再表示時は onSync する", () => {
106
+ const onSync = vi.fn();
107
+ renderHook(() =>
108
+ usePeriodicSync({
109
+ enabled: true,
110
+ intervalMs: 10_000,
111
+ onSync,
112
+ skipInitialSync: true,
113
+ }),
114
+ );
115
+
116
+ Object.defineProperty(document, "visibilityState", {
117
+ configurable: true,
118
+ value: "hidden",
119
+ });
120
+ document.dispatchEvent(new Event("visibilitychange"));
121
+ Object.defineProperty(document, "visibilityState", {
122
+ configurable: true,
123
+ value: "visible",
124
+ });
125
+ document.dispatchEvent(new Event("visibilitychange"));
126
+ expect(onSync).toHaveBeenCalledTimes(1);
127
+ });
128
+
129
+ it("skipInitialSync でも後から enabled が有効化された場合は即時 sync する", () => {
130
+ const onSync = vi.fn();
131
+ const { rerender } = renderHook(
132
+ ({ enabled }) =>
133
+ usePeriodicSync({
134
+ enabled,
135
+ intervalMs: 10_000,
136
+ onSync,
137
+ skipInitialSync: true,
138
+ }),
139
+ { initialProps: { enabled: false } },
140
+ );
141
+
142
+ expect(onSync).not.toHaveBeenCalled();
143
+
144
+ rerender({ enabled: true });
145
+ expect(onSync).toHaveBeenCalledTimes(1);
146
+ });
147
+
148
+ it("非同期 onSync が in-flight の間は interval で並列実行しない(#937)", async () => {
149
+ let resolveSync: (() => void) | undefined;
150
+ const onSync = vi.fn(
151
+ () =>
152
+ new Promise<void>((resolve) => {
153
+ resolveSync = resolve;
154
+ }),
155
+ );
156
+ renderHook(() =>
157
+ usePeriodicSync({ enabled: true, intervalMs: 10_000, onSync }),
158
+ );
159
+
160
+ // 初回即時 sync で 1 回呼ばれ in-flight になる
161
+ expect(onSync).toHaveBeenCalledTimes(1);
162
+
163
+ // in-flight 中は interval が複数回発火しても並列実行しない
164
+ await vi.advanceTimersByTimeAsync(30_000);
165
+ expect(onSync).toHaveBeenCalledTimes(1);
166
+
167
+ // 前回 onSync 完了後は次回 interval で実行される
168
+ resolveSync?.();
169
+ await Promise.resolve();
170
+ await Promise.resolve();
171
+ await vi.advanceTimersByTimeAsync(10_000);
172
+ expect(onSync).toHaveBeenCalledTimes(2);
173
+ });
174
+
175
+ it("非同期 onSync が in-flight の間はタブ再表示でも並列実行しない(#937)", async () => {
176
+ let resolveSync: (() => void) | undefined;
177
+ const onSync = vi.fn(
178
+ () =>
179
+ new Promise<void>((resolve) => {
180
+ resolveSync = resolve;
181
+ }),
182
+ );
183
+ renderHook(() =>
184
+ usePeriodicSync({ enabled: true, intervalMs: 10_000, onSync }),
185
+ );
186
+
187
+ expect(onSync).toHaveBeenCalledTimes(1); // 初回即時 sync(in-flight)
188
+
189
+ // in-flight 中の visibility 復帰では再実行しない
190
+ Object.defineProperty(document, "visibilityState", { configurable: true, value: "hidden" });
191
+ document.dispatchEvent(new Event("visibilitychange"));
192
+ Object.defineProperty(document, "visibilityState", { configurable: true, value: "visible" });
193
+ document.dispatchEvent(new Event("visibilitychange"));
194
+ expect(onSync).toHaveBeenCalledTimes(1);
195
+
196
+ // 完了後の visibility 復帰では実行する
197
+ resolveSync?.();
198
+ await Promise.resolve();
199
+ await Promise.resolve();
200
+ Object.defineProperty(document, "visibilityState", { configurable: true, value: "hidden" });
201
+ document.dispatchEvent(new Event("visibilitychange"));
202
+ Object.defineProperty(document, "visibilityState", { configurable: true, value: "visible" });
203
+ document.dispatchEvent(new Event("visibilitychange"));
204
+ expect(onSync).toHaveBeenCalledTimes(2);
205
+ });
206
+
207
+ it("同期 onSync は in-flight ガードの影響を受けず毎 interval 実行する", () => {
208
+ const onSync = vi.fn(); // void 返却(同期)
209
+ renderHook(() =>
210
+ usePeriodicSync({ enabled: true, intervalMs: 10_000, onSync }),
211
+ );
212
+
213
+ expect(onSync).toHaveBeenCalledTimes(1); // 初回即時
214
+ vi.advanceTimersByTime(30_000);
215
+ expect(onSync).toHaveBeenCalledTimes(4); // 初回 + interval 3 回
216
+ });
217
+
218
+ it("in-flight 中に enabled 再有効化で再setup された場合は即時 sync を抑止しない(#937 / PR #953)", () => {
219
+ let resolveSync: (() => void) | undefined;
220
+ const onSync = vi.fn(
221
+ () =>
222
+ new Promise<void>((resolve) => {
223
+ resolveSync = resolve;
224
+ }),
225
+ );
226
+ const { rerender } = renderHook(
227
+ ({ enabled }) => usePeriodicSync({ enabled, intervalMs: 10_000, onSync }),
228
+ { initialProps: { enabled: true } },
229
+ );
230
+
231
+ // enabled=true の初回即時 sync で in-flight(未 resolve のまま)
232
+ expect(onSync).toHaveBeenCalledTimes(1);
233
+
234
+ // 前回 onSync が in-flight のまま enabled を false→true へトグル(effect 再setup)
235
+ rerender({ enabled: false });
236
+ rerender({ enabled: true });
237
+
238
+ // in-flight 追跡が effect スコープのため、再setup 直後の即時 sync は抑止されない
239
+ expect(onSync).toHaveBeenCalledTimes(2);
240
+
241
+ // 後始末(未解決 Promise の解放)
242
+ resolveSync?.();
243
+ });
244
+ });
@@ -0,0 +1,83 @@
1
+ import { useEffect, useRef } from "react";
2
+
3
+ type Options = {
4
+ enabled: boolean;
5
+ intervalMs: number;
6
+ onSync: () => void | Promise<void>;
7
+ /**
8
+ * 初回マウント時の即時 sync を抑制する。consumer 側がマウント時に独自の初回 fetch を
9
+ * 行う場合、即時 sync と重複して同一 API が二重 dispatch されるのを避ける(#929)。
10
+ * マウント後に enabled が false→true へ切り替わった場合は対象外で、即時 sync する。
11
+ *
12
+ * 注意: React StrictMode 有効の dev ビルドでは effect が setup→cleanup→setup と
13
+ * 二重実行されるため、2 回目の setup で初回 sync が発火し抑制が無効化される。
14
+ * 本番ビルドは二重実行されないため抑制は機能する(受け入れ条件 REQ-016-AC-08 は本番挙動が対象)。
15
+ */
16
+ skipInitialSync?: boolean;
17
+ };
18
+
19
+ /**
20
+ * 一定間隔で onSync を実行する。タブ非表示中は停止し、再表示時に即時 sync する。
21
+ */
22
+ export function usePeriodicSync({
23
+ enabled,
24
+ intervalMs,
25
+ onSync,
26
+ skipInitialSync = false,
27
+ }: Options): void {
28
+ const onSyncRef = useRef(onSync);
29
+ const isInitialMountRef = useRef(true);
30
+
31
+ useEffect(() => {
32
+ onSyncRef.current = onSync;
33
+ }, [onSync]);
34
+
35
+ useEffect(() => {
36
+ const isInitialMount = isInitialMountRef.current;
37
+ isInitialMountRef.current = false;
38
+
39
+ if (!enabled || intervalMs <= 0) return;
40
+
41
+ let cancelled = false;
42
+ // この effect インスタンス内で前回の非同期 onSync が in-flight かどうか。
43
+ // effect スコープに閉じることで、enabled 等の変化で再setup された場合は状態を
44
+ // 引き継がず、再setup 直後の即時 sync を抑止しない(#937 / PR #953 レビュー対応)
45
+ let isSyncing = false;
46
+
47
+ const runSync = () => {
48
+ if (cancelled || document.visibilityState === "hidden") return;
49
+ // 前回の非同期 onSync が完了するまでは重複実行をスキップ(#937)。
50
+ // 同期 onSync(void 返却)は即時完了するため対象外。
51
+ if (isSyncing) return;
52
+ const result = onSyncRef.current();
53
+ if (result instanceof Promise) {
54
+ isSyncing = true;
55
+ void result
56
+ .catch(() => {
57
+ // onSync 内部のエラーは consumer 側で処理する。ここでは in-flight フラグの解放のみ行う
58
+ })
59
+ .finally(() => {
60
+ isSyncing = false;
61
+ });
62
+ }
63
+ };
64
+
65
+ const handleVisibility = () => {
66
+ if (document.visibilityState === "visible") {
67
+ runSync();
68
+ }
69
+ };
70
+
71
+ if (!(skipInitialSync && isInitialMount)) {
72
+ runSync();
73
+ }
74
+ const timer = window.setInterval(runSync, intervalMs);
75
+ document.addEventListener("visibilitychange", handleVisibility);
76
+
77
+ return () => {
78
+ cancelled = true;
79
+ window.clearInterval(timer);
80
+ document.removeEventListener("visibilitychange", handleVisibility);
81
+ };
82
+ }, [enabled, intervalMs, skipInitialSync]);
83
+ }
@@ -0,0 +1,251 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
+ import { renderHook, act, render, fireEvent } from "@testing-library/react";
3
+ import { createElement } from "react";
4
+ import {
5
+ PANEL_WIDTH_SAVE_DEBOUNCE_MS,
6
+ RIGHT_PANEL_LAYOUT,
7
+ saveStoredPanelWidth,
8
+ } from "@/lib/resizable-panel";
9
+ import { useResizablePanelWidth } from "./useResizablePanelWidth";
10
+
11
+ vi.mock("@/lib/resizable-panel", async (importOriginal) => {
12
+ const actual = await importOriginal<typeof import("@/lib/resizable-panel")>();
13
+ return {
14
+ ...actual,
15
+ saveStoredPanelWidth: vi.fn(),
16
+ };
17
+ });
18
+
19
+ const mockSave = vi.mocked(saveStoredPanelWidth);
20
+
21
+ function ResizeHarness({
22
+ edge = "inline-start" as const,
23
+ dir,
24
+ }: {
25
+ edge?: "inline-start" | "inline-end";
26
+ dir?: "ltr" | "rtl";
27
+ }) {
28
+ const panel = useResizablePanelWidth({
29
+ ...RIGHT_PANEL_LAYOUT,
30
+ edge,
31
+ });
32
+ return createElement(
33
+ "div",
34
+ {
35
+ "data-testid": "resize-handle",
36
+ dir,
37
+ onPointerDown: panel.onResizePointerDown,
38
+ },
39
+ panel.width,
40
+ );
41
+ }
42
+
43
+ describe("useResizablePanelWidth", () => {
44
+ beforeEach(() => {
45
+ vi.clearAllMocks();
46
+ vi.useFakeTimers();
47
+ localStorage.clear();
48
+ document.body.style.cursor = "";
49
+ document.body.style.userSelect = "";
50
+ });
51
+
52
+ afterEach(() => {
53
+ vi.useRealTimers();
54
+ document.body.style.cursor = "";
55
+ document.body.style.userSelect = "";
56
+ });
57
+
58
+ it("applyWidth は debounce 後に localStorage へ保存する", () => {
59
+ const { result } = renderHook(() =>
60
+ useResizablePanelWidth({
61
+ ...RIGHT_PANEL_LAYOUT,
62
+ edge: "inline-start",
63
+ }),
64
+ );
65
+
66
+ mockSave.mockClear();
67
+
68
+ act(() => {
69
+ result.current.applyWidth(400);
70
+ });
71
+
72
+ expect(mockSave).not.toHaveBeenCalled();
73
+
74
+ act(() => {
75
+ vi.advanceTimersByTime(PANEL_WIDTH_SAVE_DEBOUNCE_MS);
76
+ });
77
+
78
+ expect(mockSave).toHaveBeenCalledWith(RIGHT_PANEL_LAYOUT.storageKey, 400);
79
+ });
80
+
81
+ it("連続 applyWidth は最後の幅のみ debounce 保存する", () => {
82
+ const { result } = renderHook(() =>
83
+ useResizablePanelWidth({
84
+ ...RIGHT_PANEL_LAYOUT,
85
+ edge: "inline-start",
86
+ }),
87
+ );
88
+
89
+ mockSave.mockClear();
90
+
91
+ act(() => {
92
+ result.current.applyWidth(360);
93
+ result.current.applyWidth(376);
94
+ result.current.applyWidth(392);
95
+ });
96
+
97
+ act(() => {
98
+ vi.advanceTimersByTime(PANEL_WIDTH_SAVE_DEBOUNCE_MS);
99
+ });
100
+
101
+ expect(mockSave).toHaveBeenCalledTimes(1);
102
+ expect(mockSave).toHaveBeenCalledWith(RIGHT_PANEL_LAYOUT.storageKey, 392);
103
+ });
104
+
105
+ it("初回マウントでは debounce 保存しない", () => {
106
+ renderHook(() =>
107
+ useResizablePanelWidth({
108
+ ...RIGHT_PANEL_LAYOUT,
109
+ edge: "inline-start",
110
+ }),
111
+ );
112
+
113
+ mockSave.mockClear();
114
+
115
+ act(() => {
116
+ vi.advanceTimersByTime(PANEL_WIDTH_SAVE_DEBOUNCE_MS);
117
+ });
118
+
119
+ expect(mockSave).not.toHaveBeenCalled();
120
+ });
121
+
122
+ it("アンマウント時に pending save をフラッシュする", () => {
123
+ const { result, unmount } = renderHook(() =>
124
+ useResizablePanelWidth({
125
+ ...RIGHT_PANEL_LAYOUT,
126
+ edge: "inline-start",
127
+ }),
128
+ );
129
+
130
+ mockSave.mockClear();
131
+
132
+ act(() => {
133
+ result.current.applyWidth(400);
134
+ });
135
+
136
+ expect(mockSave).not.toHaveBeenCalled();
137
+
138
+ unmount();
139
+
140
+ expect(mockSave).toHaveBeenCalledWith(RIGHT_PANEL_LAYOUT.storageKey, 400);
141
+ });
142
+
143
+ it("幅未変更のアンマウントでは localStorage を再書き込みしない", () => {
144
+ const { unmount } = renderHook(() =>
145
+ useResizablePanelWidth({
146
+ ...RIGHT_PANEL_LAYOUT,
147
+ edge: "inline-start",
148
+ }),
149
+ );
150
+
151
+ mockSave.mockClear();
152
+ unmount();
153
+ expect(mockSave).not.toHaveBeenCalled();
154
+ });
155
+
156
+ it("beforeunload で pending save をフラッシュする", () => {
157
+ const { result } = renderHook(() =>
158
+ useResizablePanelWidth({
159
+ ...RIGHT_PANEL_LAYOUT,
160
+ edge: "inline-start",
161
+ }),
162
+ );
163
+
164
+ mockSave.mockClear();
165
+
166
+ act(() => {
167
+ result.current.applyWidth(400);
168
+ });
169
+
170
+ act(() => {
171
+ window.dispatchEvent(new Event("beforeunload"));
172
+ });
173
+
174
+ expect(mockSave).toHaveBeenCalledWith(RIGHT_PANEL_LAYOUT.storageKey, 400);
175
+ });
176
+
177
+ it("ポインタドラッグ終了時に flushSave する", () => {
178
+ const { getByTestId } = render(createElement(ResizeHarness));
179
+ const handle = getByTestId("resize-handle");
180
+ handle.setPointerCapture = vi.fn();
181
+ handle.releasePointerCapture = vi.fn();
182
+
183
+ mockSave.mockClear();
184
+
185
+ act(() => {
186
+ fireEvent.pointerDown(handle, { clientX: 100, pointerId: 1 });
187
+ fireEvent.pointerMove(window, { clientX: 120, pointerId: 1 });
188
+ fireEvent.pointerUp(window, { clientX: 120, pointerId: 1 });
189
+ });
190
+
191
+ expect(mockSave).toHaveBeenCalledWith(RIGHT_PANEL_LAYOUT.storageKey, 300);
192
+ });
193
+
194
+ it("#1009 RTL: inline-start エッジは物理ドラッグ符号が反転する(右ドラッグで拡大)", () => {
195
+ const { getByTestId } = render(createElement(ResizeHarness, { dir: "rtl" }));
196
+ const handle = getByTestId("resize-handle");
197
+ handle.setPointerCapture = vi.fn();
198
+ handle.releasePointerCapture = vi.fn();
199
+
200
+ mockSave.mockClear();
201
+
202
+ act(() => {
203
+ fireEvent.pointerDown(handle, { clientX: 100, pointerId: 1 });
204
+ fireEvent.pointerMove(window, { clientX: 120, pointerId: 1 });
205
+ fireEvent.pointerUp(window, { clientX: 120, pointerId: 1 });
206
+ });
207
+
208
+ // LTR では 320-20=300。RTL では符号反転で 320+20=340。
209
+ expect(mockSave).toHaveBeenCalledWith(RIGHT_PANEL_LAYOUT.storageKey, 340);
210
+ });
211
+
212
+ it("ドラッグ中のアンマウントで body style とリスナーを cleanup する", () => {
213
+ const removeListenerSpy = vi.spyOn(window, "removeEventListener");
214
+ const { getByTestId, unmount } = render(createElement(ResizeHarness));
215
+ const handle = getByTestId("resize-handle");
216
+ handle.setPointerCapture = vi.fn();
217
+ handle.releasePointerCapture = vi.fn();
218
+
219
+ act(() => {
220
+ fireEvent.pointerDown(handle, { clientX: 100, pointerId: 1 });
221
+ });
222
+
223
+ expect(document.body.style.cursor).toBe("col-resize");
224
+
225
+ unmount();
226
+
227
+ expect(document.body.style.cursor).toBe("");
228
+ expect(document.body.style.userSelect).toBe("");
229
+ expect(removeListenerSpy).toHaveBeenCalledWith("pointermove", expect.any(Function));
230
+ expect(removeListenerSpy).toHaveBeenCalledWith("pointerup", expect.any(Function));
231
+ removeListenerSpy.mockRestore();
232
+ });
233
+
234
+ it("ドラッグ中のアンマウントで変更幅をフラッシュ保存する", () => {
235
+ const { getByTestId, unmount } = render(createElement(ResizeHarness));
236
+ const handle = getByTestId("resize-handle");
237
+ handle.setPointerCapture = vi.fn();
238
+ handle.releasePointerCapture = vi.fn();
239
+
240
+ mockSave.mockClear();
241
+
242
+ act(() => {
243
+ fireEvent.pointerDown(handle, { clientX: 100, pointerId: 1 });
244
+ fireEvent.pointerMove(window, { clientX: 120, pointerId: 1 });
245
+ });
246
+
247
+ unmount();
248
+
249
+ expect(mockSave).toHaveBeenCalledWith(RIGHT_PANEL_LAYOUT.storageKey, 300);
250
+ });
251
+ });