@ripla/godd-mcp 1.0.4-canary.8 → 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 -43
  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,481 @@
1
+ import { describe, it, expect, vi, beforeEach } from "vitest";
2
+ import { render, screen, fireEvent, act } from "@testing-library/react";
3
+ import PanelResizeHandle from "./PanelResizeHandle";
4
+
5
+ describe("PanelResizeHandle", () => {
6
+ const onWidthChange = vi.fn();
7
+ const defaultResize = {
8
+ width: 320,
9
+ minWidth: 240,
10
+ maxWidth: 560,
11
+ onWidthChange,
12
+ };
13
+
14
+ beforeEach(() => {
15
+ vi.clearAllMocks();
16
+ });
17
+
18
+ it("slider ロールと aria 属性を持つ", () => {
19
+ render(
20
+ <PanelResizeHandle
21
+ edge="inline-start"
22
+ ariaLabel="コメントパネルの幅を変更"
23
+ onPointerDown={vi.fn()}
24
+ {...defaultResize}
25
+ />,
26
+ );
27
+
28
+ const handle = screen.getByRole("slider", { name: "コメントパネルの幅を変更" });
29
+ expect(handle.getAttribute("aria-orientation")).toBe("horizontal");
30
+ expect(handle.getAttribute("tabindex")).toBe("0");
31
+ expect(handle.getAttribute("aria-valuenow")).toBe("320");
32
+ expect(handle.getAttribute("aria-valuetext")).toBe("320px");
33
+ expect(handle.getAttribute("aria-valuemin")).toBe("240");
34
+ expect(handle.getAttribute("aria-valuemax")).toBe("560");
35
+ });
36
+
37
+ it("pointerdown を親へ通知する", () => {
38
+ const onPointerDown = vi.fn();
39
+ render(
40
+ <PanelResizeHandle
41
+ edge="inline-end"
42
+ ariaLabel="左サイドバーの幅を変更"
43
+ onPointerDown={onPointerDown}
44
+ {...defaultResize}
45
+ />,
46
+ );
47
+
48
+ fireEvent.pointerDown(screen.getByRole("slider"));
49
+ expect(onPointerDown).toHaveBeenCalledTimes(1);
50
+ });
51
+
52
+ it("REQ-UI-AC-06: ArrowLeft 後に aria-valuenow が新しい幅を反映する", () => {
53
+ const { rerender } = render(
54
+ <PanelResizeHandle
55
+ edge="inline-start"
56
+ ariaLabel="コメントパネルの幅を変更"
57
+ onPointerDown={vi.fn()}
58
+ {...defaultResize}
59
+ />,
60
+ );
61
+
62
+ const handle = screen.getByRole("slider");
63
+ expect(handle.getAttribute("aria-valuenow")).toBe("320");
64
+
65
+ act(() => {
66
+ fireEvent.keyDown(handle, { key: "ArrowLeft" });
67
+ });
68
+ expect(onWidthChange).toHaveBeenCalledWith(336);
69
+
70
+ rerender(
71
+ <PanelResizeHandle
72
+ edge="inline-start"
73
+ ariaLabel="コメントパネルの幅を変更"
74
+ onPointerDown={vi.fn()}
75
+ {...defaultResize}
76
+ width={336}
77
+ />,
78
+ );
79
+ expect(handle.getAttribute("aria-valuenow")).toBe("336");
80
+ });
81
+
82
+ it("左端ハンドルで ArrowLeft により幅を広げる", () => {
83
+ render(
84
+ <PanelResizeHandle
85
+ edge="inline-start"
86
+ ariaLabel="コメントパネルの幅を変更"
87
+ onPointerDown={vi.fn()}
88
+ {...defaultResize}
89
+ />,
90
+ );
91
+
92
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowLeft" });
93
+ expect(onWidthChange).toHaveBeenCalledWith(336);
94
+ });
95
+
96
+ it("左端ハンドルで ArrowRight により幅を狭める", () => {
97
+ render(
98
+ <PanelResizeHandle
99
+ edge="inline-start"
100
+ ariaLabel="コメントパネルの幅を変更"
101
+ onPointerDown={vi.fn()}
102
+ {...defaultResize}
103
+ />,
104
+ );
105
+
106
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowRight" });
107
+ expect(onWidthChange).toHaveBeenCalledWith(304);
108
+ });
109
+
110
+ it("右端ハンドルで ArrowLeft により幅を狭める", () => {
111
+ render(
112
+ <PanelResizeHandle
113
+ edge="inline-end"
114
+ ariaLabel="左サイドバーの幅を変更"
115
+ onPointerDown={vi.fn()}
116
+ {...defaultResize}
117
+ />,
118
+ );
119
+
120
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowLeft" });
121
+ expect(onWidthChange).toHaveBeenCalledWith(304);
122
+ });
123
+
124
+ it("右端ハンドルで ArrowRight により幅を広げる", () => {
125
+ render(
126
+ <PanelResizeHandle
127
+ edge="inline-end"
128
+ ariaLabel="左サイドバーの幅を変更"
129
+ onPointerDown={vi.fn()}
130
+ {...defaultResize}
131
+ />,
132
+ );
133
+
134
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowRight" });
135
+ expect(onWidthChange).toHaveBeenCalledWith(336);
136
+ });
137
+
138
+ it("max 境界で ArrowLeft を押しても onWidthChange を呼ばない", () => {
139
+ render(
140
+ <PanelResizeHandle
141
+ edge="inline-start"
142
+ ariaLabel="コメントパネルの幅を変更"
143
+ onPointerDown={vi.fn()}
144
+ {...defaultResize}
145
+ width={560}
146
+ />,
147
+ );
148
+
149
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowLeft" });
150
+ expect(onWidthChange).not.toHaveBeenCalled();
151
+ });
152
+
153
+ it("min 境界で ArrowRight を押しても onWidthChange を呼ばない", () => {
154
+ render(
155
+ <PanelResizeHandle
156
+ edge="inline-start"
157
+ ariaLabel="コメントパネルの幅を変更"
158
+ onPointerDown={vi.fn()}
159
+ {...defaultResize}
160
+ width={240}
161
+ />,
162
+ );
163
+
164
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowRight" });
165
+ expect(onWidthChange).not.toHaveBeenCalled();
166
+ });
167
+
168
+ it("右端ハンドル max 境界で ArrowRight を押しても onWidthChange を呼ばない", () => {
169
+ render(
170
+ <PanelResizeHandle
171
+ edge="inline-end"
172
+ ariaLabel="左サイドバーの幅を変更"
173
+ onPointerDown={vi.fn()}
174
+ {...defaultResize}
175
+ width={560}
176
+ />,
177
+ );
178
+
179
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowRight" });
180
+ expect(onWidthChange).not.toHaveBeenCalled();
181
+ });
182
+
183
+ it("右端ハンドル min 境界で ArrowLeft を押しても onWidthChange を呼ばない", () => {
184
+ render(
185
+ <PanelResizeHandle
186
+ edge="inline-end"
187
+ ariaLabel="左サイドバーの幅を変更"
188
+ onPointerDown={vi.fn()}
189
+ {...defaultResize}
190
+ width={240}
191
+ />,
192
+ );
193
+
194
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowLeft" });
195
+ expect(onWidthChange).not.toHaveBeenCalled();
196
+ });
197
+
198
+ it("Home キーで minWidth にジャンプする", () => {
199
+ render(
200
+ <PanelResizeHandle
201
+ edge="inline-start"
202
+ ariaLabel="コメントパネルの幅を変更"
203
+ onPointerDown={vi.fn()}
204
+ {...defaultResize}
205
+ />,
206
+ );
207
+
208
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "Home" });
209
+ expect(onWidthChange).toHaveBeenCalledWith(240);
210
+ });
211
+
212
+ it("End キーで maxWidth にジャンプする", () => {
213
+ render(
214
+ <PanelResizeHandle
215
+ edge="inline-start"
216
+ ariaLabel="コメントパネルの幅を変更"
217
+ onPointerDown={vi.fn()}
218
+ {...defaultResize}
219
+ />,
220
+ );
221
+
222
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "End" });
223
+ expect(onWidthChange).toHaveBeenCalledWith(560);
224
+ });
225
+
226
+ it("Home で既に minWidth のとき onWidthChange を呼ばない", () => {
227
+ render(
228
+ <PanelResizeHandle
229
+ edge="inline-start"
230
+ ariaLabel="コメントパネルの幅を変更"
231
+ onPointerDown={vi.fn()}
232
+ {...defaultResize}
233
+ width={240}
234
+ />,
235
+ );
236
+
237
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "Home" });
238
+ expect(onWidthChange).not.toHaveBeenCalled();
239
+ });
240
+
241
+ it("End で既に maxWidth のとき onWidthChange を呼ばない", () => {
242
+ render(
243
+ <PanelResizeHandle
244
+ edge="inline-start"
245
+ ariaLabel="コメントパネルの幅を変更"
246
+ onPointerDown={vi.fn()}
247
+ {...defaultResize}
248
+ width={560}
249
+ />,
250
+ );
251
+
252
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "End" });
253
+ expect(onWidthChange).not.toHaveBeenCalled();
254
+ });
255
+
256
+ it("右端ハンドルで Home 時に既に minWidth のとき onWidthChange を呼ばない", () => {
257
+ render(
258
+ <PanelResizeHandle
259
+ edge="inline-end"
260
+ ariaLabel="左サイドバーの幅を変更"
261
+ onPointerDown={vi.fn()}
262
+ {...defaultResize}
263
+ width={240}
264
+ />,
265
+ );
266
+
267
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "Home" });
268
+ expect(onWidthChange).not.toHaveBeenCalled();
269
+ });
270
+
271
+ it("右端ハンドルで End 時に既に maxWidth のとき onWidthChange を呼ばない", () => {
272
+ render(
273
+ <PanelResizeHandle
274
+ edge="inline-end"
275
+ ariaLabel="左サイドバーの幅を変更"
276
+ onPointerDown={vi.fn()}
277
+ {...defaultResize}
278
+ width={560}
279
+ />,
280
+ );
281
+
282
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "End" });
283
+ expect(onWidthChange).not.toHaveBeenCalled();
284
+ });
285
+
286
+ it("左端ハンドルはパネル境界線上に straddle 配置する", () => {
287
+ render(
288
+ <PanelResizeHandle
289
+ edge="inline-start"
290
+ ariaLabel="コメントパネルの幅を変更"
291
+ onPointerDown={vi.fn()}
292
+ {...defaultResize}
293
+ />,
294
+ );
295
+
296
+ const handle = screen.getByRole("slider", { name: "コメントパネルの幅を変更" });
297
+ expect(handle.className).toContain("-start-1.5");
298
+ expect(handle.className).toContain("w-3");
299
+ });
300
+
301
+ it("右端ハンドルはパネル境界線上に straddle 配置する", () => {
302
+ render(
303
+ <PanelResizeHandle
304
+ edge="inline-end"
305
+ ariaLabel="左サイドバーの幅を変更"
306
+ onPointerDown={vi.fn()}
307
+ {...defaultResize}
308
+ />,
309
+ );
310
+
311
+ const handle = screen.getByRole("slider", { name: "左サイドバーの幅を変更" });
312
+ expect(handle.className).toContain("-end-1.5");
313
+ expect(handle.className).toContain("w-3");
314
+ });
315
+
316
+ it("REQ-UI-AC-05 (#991/#1009): inline-end エッジの視覚線を start-1/2 起点で inline-end 側 border に重ねる(translate 無し)", () => {
317
+ render(
318
+ <PanelResizeHandle
319
+ edge="inline-end"
320
+ ariaLabel="左サイドバーの幅を変更"
321
+ onPointerDown={vi.fn()}
322
+ {...defaultResize}
323
+ />,
324
+ );
325
+
326
+ const line = screen.getByRole("slider", { name: "左サイドバーの幅を変更" }).querySelector("div");
327
+ expect(line).not.toBeNull();
328
+ const classes = line?.classList;
329
+ // border は padding-edge の外側 1px に描画される。論理 inset `start-1/2` で線の inline-start 端を
330
+ // 中心(50%)に固定すれば inline-end 側へ 1px 展開して border に重なり、translate 無しで dir 反転する。
331
+ // class トークン完全一致で検証し、部分一致による誤検知を避ける
332
+ expect(classes?.contains("start-1/2")).toBe(true);
333
+ expect(classes?.contains("end-1/2")).toBe(false);
334
+ // 物理 translate は使わない(論理 inset で left/right を吸収するため)
335
+ expect(classes?.contains("translate-x-0")).toBe(false);
336
+ expect(classes?.contains("-translate-x-full")).toBe(false);
337
+ expect(classes?.contains("-translate-x-1/2")).toBe(false);
338
+ // hover / drag のインジケーター色は維持
339
+ expect(classes?.contains("group-hover:bg-blue-500/70")).toBe(true);
340
+ expect(classes?.contains("group-active:bg-blue-400")).toBe(true);
341
+ });
342
+
343
+ it("REQ-UI-AC-05 (#991/#1009): inline-start エッジの視覚線を end-1/2 起点で inline-start 側 border に重ねる(translate 無し)", () => {
344
+ render(
345
+ <PanelResizeHandle
346
+ edge="inline-start"
347
+ ariaLabel="コメントパネルの幅を変更"
348
+ onPointerDown={vi.fn()}
349
+ {...defaultResize}
350
+ />,
351
+ );
352
+
353
+ const line = screen.getByRole("slider", { name: "コメントパネルの幅を変更" }).querySelector("div");
354
+ expect(line).not.toBeNull();
355
+ const classes = line?.classList;
356
+ // 論理 inset `end-1/2` で線の inline-end 端を中心(50%)に固定すれば inline-start 側へ 1px 展開して
357
+ // border に重なり、translate 無しで dir 反転する。
358
+ // class トークン完全一致で検証し、部分一致による誤検知を避ける
359
+ expect(classes?.contains("end-1/2")).toBe(true);
360
+ expect(classes?.contains("start-1/2")).toBe(false);
361
+ // 物理 translate は使わない
362
+ expect(classes?.contains("-translate-x-full")).toBe(false);
363
+ expect(classes?.contains("translate-x-0")).toBe(false);
364
+ expect(classes?.contains("-translate-x-1/2")).toBe(false);
365
+ // hover / drag のインジケーター色は維持
366
+ expect(classes?.contains("group-hover:bg-blue-500/70")).toBe(true);
367
+ expect(classes?.contains("group-active:bg-blue-400")).toBe(true);
368
+ });
369
+
370
+ it("REQ-UI-AC-11 (#992): キーボードフォーカスを受け取れる(tabIndex=0 で focus 到達)", () => {
371
+ render(
372
+ <PanelResizeHandle
373
+ edge="inline-end"
374
+ ariaLabel="左サイドバーの幅を変更"
375
+ onPointerDown={vi.fn()}
376
+ {...defaultResize}
377
+ />,
378
+ );
379
+
380
+ const handle = screen.getByRole("slider", { name: "左サイドバーの幅を変更" });
381
+ handle.focus();
382
+ expect(document.activeElement).toBe(handle);
383
+ });
384
+
385
+ it("REQ-UI-AC-11 (#992): フォーカス時に内側視覚線が青くなる(overflow にクリップされない可視インジケーター)", () => {
386
+ render(
387
+ <PanelResizeHandle
388
+ edge="inline-end"
389
+ ariaLabel="左サイドバーの幅を変更"
390
+ onPointerDown={vi.fn()}
391
+ {...defaultResize}
392
+ />,
393
+ );
394
+
395
+ const line = screen.getByRole("slider", { name: "左サイドバーの幅を変更" }).querySelector("div");
396
+ expect(line).not.toBeNull();
397
+ const classes = line?.classList;
398
+ // 全高・border 上の内側線にフォーカス色を付与することで、祖先 overflow-hidden に
399
+ // クリップされ得るリングに依存せず確実に可視なフォーカスインジケーターとする。
400
+ // hover(blue-500/70) と弁別するため明色 blue-400(不透明)、WCAG 2.4.11 のため 2px(w-0.5)。
401
+ expect(classes?.contains("group-focus-visible:bg-blue-400")).toBe(true);
402
+ expect(classes?.contains("group-focus-visible:w-0.5")).toBe(true);
403
+ // hover 色とは別トークンで弁別可能であること
404
+ expect(classes?.contains("group-focus-visible:bg-blue-500/70")).toBe(false);
405
+ });
406
+
407
+ it("REQ-UI-AC-11 (#992): 左端ハンドルもフォーカス可能でフォーカス色を持つ", () => {
408
+ render(
409
+ <PanelResizeHandle
410
+ edge="inline-start"
411
+ ariaLabel="コメントパネルの幅を変更"
412
+ onPointerDown={vi.fn()}
413
+ {...defaultResize}
414
+ />,
415
+ );
416
+
417
+ const handle = screen.getByRole("slider", { name: "コメントパネルの幅を変更" });
418
+ handle.focus();
419
+ expect(document.activeElement).toBe(handle);
420
+
421
+ const line = handle.querySelector("div");
422
+ expect(line?.classList.contains("group-focus-visible:bg-blue-400")).toBe(true);
423
+ expect(line?.classList.contains("group-focus-visible:w-0.5")).toBe(true);
424
+ });
425
+ });
426
+
427
+ describe("PanelResizeHandle RTL(#1009 / REQ-UI-AC-12)", () => {
428
+ const onWidthChange = vi.fn();
429
+ const defaultResize = {
430
+ width: 320,
431
+ minWidth: 240,
432
+ maxWidth: 560,
433
+ onWidthChange,
434
+ };
435
+
436
+ beforeEach(() => {
437
+ vi.clearAllMocks();
438
+ });
439
+
440
+ // dir="rtl" 祖先を与え isRtlContext を rtl 判定させる。物理矢印キーの拡大方向が LTR と逆転する。
441
+ function renderRtl(edge: "inline-start" | "inline-end", ariaLabel: string) {
442
+ return render(
443
+ <div dir="rtl">
444
+ <PanelResizeHandle edge={edge} ariaLabel={ariaLabel} onPointerDown={vi.fn()} {...defaultResize} />
445
+ </div>,
446
+ );
447
+ }
448
+
449
+ it("RTL: inline-end エッジは ArrowLeft で拡大する(LTR の ArrowRight と逆)", () => {
450
+ renderRtl("inline-end", "左サイドバーの幅を変更");
451
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowLeft" });
452
+ expect(onWidthChange).toHaveBeenCalledWith(336);
453
+ });
454
+
455
+ it("RTL: inline-end エッジは ArrowRight で縮小する", () => {
456
+ renderRtl("inline-end", "左サイドバーの幅を変更");
457
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowRight" });
458
+ expect(onWidthChange).toHaveBeenCalledWith(304);
459
+ });
460
+
461
+ it("RTL: inline-start エッジは ArrowRight で拡大する(LTR の ArrowLeft と逆)", () => {
462
+ renderRtl("inline-start", "コメントパネルの幅を変更");
463
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowRight" });
464
+ expect(onWidthChange).toHaveBeenCalledWith(336);
465
+ });
466
+
467
+ it("RTL: inline-start エッジは ArrowLeft で縮小する", () => {
468
+ renderRtl("inline-start", "コメントパネルの幅を変更");
469
+ fireEvent.keyDown(screen.getByRole("slider"), { key: "ArrowLeft" });
470
+ expect(onWidthChange).toHaveBeenCalledWith(304);
471
+ });
472
+
473
+ it("RTL でも Home/End は min/max ジャンプで不変(書字方向非依存)", () => {
474
+ renderRtl("inline-start", "コメントパネルの幅を変更");
475
+ const handle = screen.getByRole("slider");
476
+ fireEvent.keyDown(handle, { key: "Home" });
477
+ expect(onWidthChange).toHaveBeenCalledWith(240);
478
+ fireEvent.keyDown(handle, { key: "End" });
479
+ expect(onWidthChange).toHaveBeenCalledWith(560);
480
+ });
481
+ });
@@ -0,0 +1,96 @@
1
+ import type { KeyboardEvent } from "react";
2
+ import {
3
+ isRtlContext,
4
+ PANEL_RESIZE_KEYBOARD_STEP_PX,
5
+ clampPanelWidth,
6
+ type PanelResizeProps,
7
+ type ResizeEdge,
8
+ } from "@/lib/resizable-panel";
9
+
10
+ type Props = {
11
+ edge: ResizeEdge;
12
+ ariaLabel: string;
13
+ } & Pick<PanelResizeProps, "onPointerDown" | "width" | "minWidth" | "maxWidth" | "onWidthChange">;
14
+
15
+ /**
16
+ * 物理矢印キーから幅の増減量を求める。拡大する物理方向は「論理エッジ × 書字方向」で決まり、
17
+ * `dir="rtl"` では LTR と逆転する(#1009)。物理ドラッグ方向と整合させるため矢印は物理キーのまま扱う。
18
+ */
19
+ function keyboardWidthDelta(edge: ResizeEdge, key: string, isRtl: boolean): number | null {
20
+ // inline-end エッジは LTR で右(ArrowRight)が拡大、RTL では反転する。
21
+ const widenOnArrowRight = (edge === "inline-end") !== isRtl;
22
+ if (key === "ArrowRight") return widenOnArrowRight ? PANEL_RESIZE_KEYBOARD_STEP_PX : -PANEL_RESIZE_KEYBOARD_STEP_PX;
23
+ if (key === "ArrowLeft") return widenOnArrowRight ? -PANEL_RESIZE_KEYBOARD_STEP_PX : PANEL_RESIZE_KEYBOARD_STEP_PX;
24
+ return null;
25
+ }
26
+
27
+ function keyboardJumpWidth(
28
+ key: string,
29
+ minWidth: number,
30
+ maxWidth: number,
31
+ ): number | null {
32
+ if (key === "Home") return minWidth;
33
+ if (key === "End") return maxWidth;
34
+ return null;
35
+ }
36
+
37
+ export default function PanelResizeHandle({
38
+ edge,
39
+ onPointerDown,
40
+ ariaLabel,
41
+ width,
42
+ minWidth,
43
+ maxWidth,
44
+ onWidthChange,
45
+ }: Props) {
46
+ // 論理 inset でハンドルをパネル境界(inline-start / inline-end)に straddle 配置する(#1009)。
47
+ const positionClass = edge === "inline-start" ? "-start-1.5" : "-end-1.5";
48
+ // 視覚線(w-px)を親 aside の border 位置に正確に重ねる(#991 / #1009)。
49
+ // ハンドル中心は親 padding-edge に一致するが、border-box の border は padding-edge の外側 1px に
50
+ // 描画される。論理 inset で border 側の端を中心(50%)に固定すれば translate 無しで重なり、
51
+ // `dir="rtl"` でも左右が自動反転する:
52
+ // - inline-end エッジ: `start-1/2` で inline-start 端を中心に置き inline-end 側へ 1px 展開
53
+ // - inline-start エッジ: `end-1/2` で inline-end 端を中心に置き inline-start 側へ 1px 展開
54
+ const lineAlignClass = edge === "inline-start" ? "end-1/2" : "start-1/2";
55
+
56
+ const handleKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
57
+ const jumpWidth = keyboardJumpWidth(event.key, minWidth, maxWidth);
58
+ if (jumpWidth != null) {
59
+ event.preventDefault();
60
+ if (jumpWidth !== width) {
61
+ onWidthChange(jumpWidth);
62
+ }
63
+ return;
64
+ }
65
+ const delta = keyboardWidthDelta(edge, event.key, isRtlContext(event.currentTarget));
66
+ if (delta == null) return;
67
+ const nextWidth = clampPanelWidth(width + delta, minWidth, maxWidth);
68
+ event.preventDefault();
69
+ if (nextWidth !== width) {
70
+ onWidthChange(nextWidth);
71
+ }
72
+ };
73
+
74
+ return (
75
+ <div
76
+ role="slider"
77
+ tabIndex={0}
78
+ aria-orientation="horizontal"
79
+ aria-label={ariaLabel}
80
+ aria-valuenow={width}
81
+ aria-valuetext={`${width}px`}
82
+ aria-valuemin={minWidth}
83
+ aria-valuemax={maxWidth}
84
+ onPointerDown={onPointerDown}
85
+ onKeyDown={handleKeyDown}
86
+ className={`hidden md:block absolute top-0 ${positionClass} z-30 h-full w-3 cursor-col-resize touch-none group outline-none focus-visible:ring-2 focus-visible:ring-blue-500/70`}
87
+ >
88
+ {/* キーボードフォーカス時は内側線(全高・border 上)を強調する(#992)。focus-visible:ring は
89
+ 祖先の overflow-hidden でクリップされ得るが、内側線はハンドル内に収まりクリップされないため、
90
+ リングに依存しない確実に可視なフォーカスインジケーターになる。
91
+ hover(blue-500/70) と弁別するため明色 blue-400(不透明)、WCAG 2.4.11(Focus Appearance)
92
+ の面積/コントラストを満たすため 2px(w-0.5) に強調する。 */}
93
+ <div className={`absolute ${lineAlignClass} top-0 h-full w-px bg-gray-700 transition-colors group-hover:bg-blue-500/70 group-active:bg-blue-400 group-focus-visible:bg-blue-400 group-focus-visible:w-0.5`} />
94
+ </div>
95
+ );
96
+ }