@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,73 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ buildMainPageSearchParams,
4
+ parseIssueSearchParam,
5
+ issueSelectionToSearchParam,
6
+ searchParamsToString,
7
+ } from "./issue-url-sync";
8
+
9
+ describe("parseIssueSearchParam", () => {
10
+ it("parses positive issue numbers", () => {
11
+ expect(parseIssueSearchParam("794")).toBe(794);
12
+ });
13
+
14
+ it("parses new issue sentinel", () => {
15
+ expect(parseIssueSearchParam("new")).toBe("new");
16
+ });
17
+
18
+ it("returns null for invalid values", () => {
19
+ expect(parseIssueSearchParam(null)).toBeNull();
20
+ expect(parseIssueSearchParam("")).toBeNull();
21
+ expect(parseIssueSearchParam("0")).toBeNull();
22
+ expect(parseIssueSearchParam("-1")).toBeNull();
23
+ expect(parseIssueSearchParam("abc")).toBeNull();
24
+ });
25
+ });
26
+
27
+ describe("issueSelectionToSearchParam", () => {
28
+ it("serializes issue selections", () => {
29
+ expect(issueSelectionToSearchParam(123)).toBe("123");
30
+ expect(issueSelectionToSearchParam("new")).toBe("new");
31
+ expect(issueSelectionToSearchParam(null)).toBeNull();
32
+ });
33
+ });
34
+
35
+ describe("buildMainPageSearchParams", () => {
36
+ it("builds issue-only deep link", () => {
37
+ const params = buildMainPageSearchParams({ issue: 794 });
38
+ expect(searchParamsToString(params)).toBe("?issue=794");
39
+ });
40
+
41
+ it("builds new-issue deep link", () => {
42
+ const params = buildMainPageSearchParams({ issue: "new" });
43
+ expect(searchParamsToString(params)).toBe("?issue=new");
44
+ });
45
+
46
+ it("prefers file over issue when both provided", () => {
47
+ const params = buildMainPageSearchParams({
48
+ file: "docs/README.md",
49
+ issue: 794,
50
+ });
51
+ expect(params.get("file")).toBe("docs/README.md");
52
+ expect(params.get("issue")).toBe("794");
53
+ });
54
+
55
+ it("preserves unrelated query params", () => {
56
+ const preserve = new URLSearchParams("x=1&file=old&issue=1");
57
+ const params = buildMainPageSearchParams({
58
+ issue: 42,
59
+ preserve,
60
+ });
61
+ expect(params.get("x")).toBe("1");
62
+ expect(params.get("issue")).toBe("42");
63
+ expect(params.has("file")).toBe(false);
64
+ });
65
+
66
+ it("clears issue and file when neither is set", () => {
67
+ const preserve = new URLSearchParams("file=a&issue=1&x=1");
68
+ const params = buildMainPageSearchParams({ preserve });
69
+ expect(params.get("x")).toBe("1");
70
+ expect(params.has("file")).toBe(false);
71
+ expect(params.has("issue")).toBe(false);
72
+ });
73
+ });
@@ -0,0 +1,43 @@
1
+ export type IssueUrlSelection = number | "new" | null;
2
+
3
+ export function parseIssueSearchParam(value: string | null): IssueUrlSelection {
4
+ if (!value) return null;
5
+ if (value === "new") return "new";
6
+ const parsed = Number(value);
7
+ if (Number.isInteger(parsed) && parsed > 0) return parsed;
8
+ return null;
9
+ }
10
+
11
+ export function issueSelectionToSearchParam(selection: IssueUrlSelection): string | null {
12
+ if (selection === null) return null;
13
+ if (selection === "new") return "new";
14
+ return String(selection);
15
+ }
16
+
17
+ export function buildMainPageSearchParams(options: {
18
+ file?: string | null;
19
+ issue?: IssueUrlSelection;
20
+ preserve?: URLSearchParams;
21
+ }): URLSearchParams {
22
+ const params = new URLSearchParams();
23
+ if (options.preserve) {
24
+ for (const [key, value] of options.preserve) {
25
+ if (key !== "file" && key !== "issue") {
26
+ params.append(key, value);
27
+ }
28
+ }
29
+ }
30
+ if (options.file) {
31
+ params.set("file", options.file);
32
+ }
33
+ const issueValue = issueSelectionToSearchParam(options.issue ?? null);
34
+ if (issueValue) {
35
+ params.set("issue", issueValue);
36
+ }
37
+ return params;
38
+ }
39
+
40
+ export function searchParamsToString(params: URLSearchParams): string {
41
+ const qs = params.toString();
42
+ return qs ? `?${qs}` : "";
43
+ }
@@ -0,0 +1,37 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import {
3
+ buildDefaultViewSearchParams,
4
+ isDefaultMainContentView,
5
+ } from "@/lib/main-page-default-view";
6
+
7
+ describe("buildDefaultViewSearchParams", () => {
8
+ it("clears file and issue deep links", () => {
9
+ const preserve = new URLSearchParams("file=docs/a.md&issue=42&tab=settings");
10
+ const params = buildDefaultViewSearchParams(preserve);
11
+ expect(params.get("file")).toBeNull();
12
+ expect(params.get("issue")).toBeNull();
13
+ expect(params.get("tab")).toBe("settings");
14
+ });
15
+ });
16
+
17
+ describe("isDefaultMainContentView", () => {
18
+ it("returns true when no file or issue is selected", () => {
19
+ expect(
20
+ isDefaultMainContentView({
21
+ selectedFile: null,
22
+ fileContent: null,
23
+ selectedIssue: null,
24
+ }),
25
+ ).toBe(true);
26
+ });
27
+
28
+ it("returns false when an issue remains selected without a file", () => {
29
+ expect(
30
+ isDefaultMainContentView({
31
+ selectedFile: null,
32
+ fileContent: null,
33
+ selectedIssue: 812,
34
+ }),
35
+ ).toBe(false);
36
+ });
37
+ });
@@ -0,0 +1,15 @@
1
+ import { buildMainPageSearchParams } from "@/lib/issue-url-sync";
2
+
3
+ /** Search params for MainPage default home (file tree tab, no file/issue selection). */
4
+ export function buildDefaultViewSearchParams(preserve?: URLSearchParams): URLSearchParams {
5
+ return buildMainPageSearchParams({ preserve });
6
+ }
7
+
8
+ /** True when the main pane should show the default empty file-tree prompt. */
9
+ export function isDefaultMainContentView(input: {
10
+ selectedFile: string | null;
11
+ fileContent: unknown | null;
12
+ selectedIssue: number | "new" | null;
13
+ }): boolean {
14
+ return !input.selectedFile && !input.fileContent && input.selectedIssue == null;
15
+ }
@@ -0,0 +1,199 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import {
3
+ LEFT_SIDEBAR_LAYOUT,
4
+ RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS,
5
+ RESIZABLE_RIGHT_PANEL_ASIDE_CLASS,
6
+ RIGHT_PANEL_LAYOUT,
7
+ clampPanelWidth,
8
+ computeResizedWidth,
9
+ isRtlContext,
10
+ loadStoredPanelWidth,
11
+ panelResizeWithoutHandle,
12
+ resolveMdUpPanelResize,
13
+ saveStoredPanelWidth,
14
+ } from "@/lib/resizable-panel";
15
+
16
+ describe("RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS", () => {
17
+ it("md+ で relative 配置し static を使わない", () => {
18
+ expect(RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS).toContain("md:relative");
19
+ expect(RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS).not.toContain("md:static");
20
+ expect(RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS).toContain("md:inset-auto");
21
+ });
22
+
23
+ it("#1009: 位置・border は論理プロパティ(start-0 / border-e)で物理 left/border-r を使わない", () => {
24
+ expect(RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS).toContain("start-0");
25
+ expect(RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS).toContain("border-e");
26
+ expect(RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS).not.toContain("left-0");
27
+ expect(RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS).not.toContain("border-r");
28
+ });
29
+ });
30
+
31
+ describe("resolveMdUpPanelResize", () => {
32
+ it("md+ のときリサイズ binding を返す", () => {
33
+ const onPointerDown = () => {};
34
+ const onWidthChange = () => {};
35
+ expect(
36
+ resolveMdUpPanelResize(true, 320, 240, 560, onPointerDown, onWidthChange),
37
+ ).toEqual({
38
+ width: 320,
39
+ minWidth: 240,
40
+ maxWidth: 560,
41
+ onPointerDown,
42
+ onWidthChange,
43
+ });
44
+ });
45
+
46
+ it("モバイルでは undefined を返す", () => {
47
+ expect(
48
+ resolveMdUpPanelResize(false, 320, 240, 560, () => {}, () => {}),
49
+ ).toBeUndefined();
50
+ });
51
+ });
52
+
53
+ describe("panelResizeWithoutHandle", () => {
54
+ it("showResizeHandle: false を付与し幅 binding は維持する", () => {
55
+ const binding = {
56
+ width: 320,
57
+ minWidth: 240,
58
+ maxWidth: 560,
59
+ onPointerDown: () => {},
60
+ onWidthChange: () => {},
61
+ };
62
+ expect(panelResizeWithoutHandle(binding)).toEqual({
63
+ ...binding,
64
+ showResizeHandle: false,
65
+ });
66
+ });
67
+ });
68
+
69
+ describe("RESIZABLE_RIGHT_PANEL_ASIDE_CLASS", () => {
70
+ it("md+ で relative 配置し static を使わない", () => {
71
+ expect(RESIZABLE_RIGHT_PANEL_ASIDE_CLASS).toContain("md:relative");
72
+ expect(RESIZABLE_RIGHT_PANEL_ASIDE_CLASS).not.toContain("md:static");
73
+ expect(RESIZABLE_RIGHT_PANEL_ASIDE_CLASS).toContain("md:inset-auto");
74
+ });
75
+
76
+ it("#1009: border は論理プロパティ(border-s)で物理 border-l を使わない", () => {
77
+ expect(RESIZABLE_RIGHT_PANEL_ASIDE_CLASS).toContain("border-s");
78
+ expect(RESIZABLE_RIGHT_PANEL_ASIDE_CLASS).not.toContain("border-l");
79
+ });
80
+ });
81
+
82
+ describe("isRtlContext", () => {
83
+ it("最も近い [dir=rtl] 祖先で true", () => {
84
+ const root = document.createElement("div");
85
+ root.setAttribute("dir", "rtl");
86
+ const child = document.createElement("span");
87
+ root.appendChild(child);
88
+ expect(isRtlContext(child)).toBe(true);
89
+ });
90
+
91
+ it("dir 指定が無ければ LTR 既定で false", () => {
92
+ const el = document.createElement("div");
93
+ expect(isRtlContext(el)).toBe(false);
94
+ });
95
+
96
+ it("より近い [dir=ltr] が rtl 祖先を上書きする", () => {
97
+ const root = document.createElement("div");
98
+ root.setAttribute("dir", "rtl");
99
+ const inner = document.createElement("div");
100
+ inner.setAttribute("dir", "ltr");
101
+ const child = document.createElement("span");
102
+ inner.appendChild(child);
103
+ root.appendChild(inner);
104
+ expect(isRtlContext(child)).toBe(false);
105
+ });
106
+
107
+ it("dir 値は大小文字非依存(RTL も rtl 扱い)", () => {
108
+ const root = document.createElement("div");
109
+ root.setAttribute("dir", "RTL");
110
+ const child = document.createElement("span");
111
+ root.appendChild(child);
112
+ expect(isRtlContext(child)).toBe(true);
113
+ });
114
+
115
+ it("null/undefined では false", () => {
116
+ expect(isRtlContext(null)).toBe(false);
117
+ expect(isRtlContext(undefined)).toBe(false);
118
+ });
119
+ });
120
+
121
+ describe("clampPanelWidth", () => {
122
+ it("min/max の範囲内に収める", () => {
123
+ expect(clampPanelWidth(100, 200, 400)).toBe(200);
124
+ expect(clampPanelWidth(500, 200, 400)).toBe(400);
125
+ expect(clampPanelWidth(321.6, 200, 400)).toBe(322);
126
+ });
127
+ });
128
+
129
+ describe("computeResizedWidth", () => {
130
+ it("inline-end エッジ(LTR)は右ドラッグで拡大・左ドラッグで縮小する", () => {
131
+ expect(
132
+ computeResizedWidth(256, 100, 140, "inline-end", 200, 480),
133
+ ).toBe(296);
134
+ expect(
135
+ computeResizedWidth(256, 100, 60, "inline-end", 200, 480),
136
+ ).toBe(216);
137
+ });
138
+
139
+ it("inline-start エッジ(LTR)は左ドラッグで拡大・右ドラッグで縮小する", () => {
140
+ expect(
141
+ computeResizedWidth(320, 900, 860, "inline-start", 240, 560),
142
+ ).toBe(360);
143
+ expect(
144
+ computeResizedWidth(320, 900, 940, "inline-start", 240, 560),
145
+ ).toBe(280);
146
+ });
147
+
148
+ it("#1009 RTL: inline-end エッジは物理符号が反転する(左ドラッグで拡大)", () => {
149
+ expect(
150
+ computeResizedWidth(256, 100, 60, "inline-end", 200, 480, true),
151
+ ).toBe(296);
152
+ expect(
153
+ computeResizedWidth(256, 100, 140, "inline-end", 200, 480, true),
154
+ ).toBe(216);
155
+ });
156
+
157
+ it("#1009 RTL: inline-start エッジは物理符号が反転する(右ドラッグで拡大)", () => {
158
+ expect(
159
+ computeResizedWidth(320, 900, 940, "inline-start", 240, 560, true),
160
+ ).toBe(360);
161
+ expect(
162
+ computeResizedWidth(320, 900, 860, "inline-start", 240, 560, true),
163
+ ).toBe(280);
164
+ });
165
+ });
166
+
167
+ describe("panel width storage", () => {
168
+ beforeEach(() => {
169
+ window.localStorage.clear();
170
+ });
171
+
172
+ afterEach(() => {
173
+ window.localStorage.clear();
174
+ });
175
+
176
+ it("保存した幅を読み込む", () => {
177
+ saveStoredPanelWidth(LEFT_SIDEBAR_LAYOUT.storageKey, 300);
178
+ expect(
179
+ loadStoredPanelWidth(
180
+ LEFT_SIDEBAR_LAYOUT.storageKey,
181
+ LEFT_SIDEBAR_LAYOUT.defaultWidth,
182
+ LEFT_SIDEBAR_LAYOUT.minWidth,
183
+ LEFT_SIDEBAR_LAYOUT.maxWidth,
184
+ ),
185
+ ).toBe(300);
186
+ });
187
+
188
+ it("不正値は default にフォールバックする", () => {
189
+ window.localStorage.setItem(RIGHT_PANEL_LAYOUT.storageKey, "invalid");
190
+ expect(
191
+ loadStoredPanelWidth(
192
+ RIGHT_PANEL_LAYOUT.storageKey,
193
+ RIGHT_PANEL_LAYOUT.defaultWidth,
194
+ RIGHT_PANEL_LAYOUT.minWidth,
195
+ RIGHT_PANEL_LAYOUT.maxWidth,
196
+ ),
197
+ ).toBe(RIGHT_PANEL_LAYOUT.defaultWidth);
198
+ });
199
+ });
@@ -0,0 +1,136 @@
1
+ import type { PointerEvent as ReactPointerEvent } from "react";
2
+
3
+ export const LEFT_SIDEBAR_LAYOUT = {
4
+ storageKey: "notes-layout-left-sidebar-width",
5
+ defaultWidth: 256,
6
+ minWidth: 200,
7
+ maxWidth: 480,
8
+ } as const;
9
+
10
+ export const RIGHT_PANEL_LAYOUT = {
11
+ storageKey: "notes-layout-right-panel-width",
12
+ defaultWidth: 320,
13
+ minWidth: 240,
14
+ maxWidth: 560,
15
+ } as const;
16
+
17
+ export const PANEL_RESIZE_KEYBOARD_STEP_PX = 16;
18
+
19
+ /** キーボード連打時の localStorage 書き込みを間引く debounce(ms) */
20
+ export const PANEL_WIDTH_SAVE_DEBOUNCE_MS = 300;
21
+
22
+ export type PanelResizeProps = {
23
+ width: number;
24
+ minWidth: number;
25
+ maxWidth: number;
26
+ onPointerDown: (event: ReactPointerEvent<HTMLDivElement>) => void;
27
+ onWidthChange: (width: number) => void;
28
+ /** false のとき幅スタイルのみ適用しリサイズハンドルは非表示(右パネル複数表示時の a11y 用) */
29
+ showResizeHandle?: boolean;
30
+ };
31
+
32
+ /** 幅 binding は維持し、フォーカス可能な slider を1つに抑える */
33
+ export function panelResizeWithoutHandle(binding: PanelResizeProps): PanelResizeProps {
34
+ return { ...binding, showResizeHandle: false };
35
+ }
36
+
37
+ /** md+ のみパネルリサイズ binding を返す(ハンドル表示条件の単一化) */
38
+ export function resolveMdUpPanelResize(
39
+ mdUpLayout: boolean,
40
+ width: number,
41
+ minWidth: number,
42
+ maxWidth: number,
43
+ onPointerDown: PanelResizeProps["onPointerDown"],
44
+ onWidthChange: PanelResizeProps["onWidthChange"],
45
+ ): PanelResizeProps | undefined {
46
+ if (!mdUpLayout) return undefined;
47
+ return { width, minWidth, maxWidth, onPointerDown, onWidthChange };
48
+ }
49
+
50
+ /**
51
+ * md+ では relative でリサイズハンドルの containing block を確保する(static 不可)。
52
+ * 位置・border は論理プロパティ(`start-0` / `border-e`)で記述し `dir="rtl"` で左右が反転する(#1009)。
53
+ */
54
+ export const RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS =
55
+ "fixed inset-y-0 start-0 z-40 w-[min(100vw,16rem)] md:w-auto bg-gray-800 border-e border-gray-700 flex flex-col shrink-0 transform transition-transform duration-200 ease-in-out md:relative md:inset-auto md:translate-x-0";
56
+
57
+ /**
58
+ * md+ では relative でリサイズハンドルの containing block を確保する(static 不可)。
59
+ * border は論理プロパティ(`border-s`)で記述し `dir="rtl"` で反転する(#1009)。
60
+ */
61
+ export const RESIZABLE_RIGHT_PANEL_ASIDE_CLASS =
62
+ "absolute inset-0 md:relative md:inset-auto md:w-auto z-20 border-s border-gray-700 bg-gray-800 flex flex-col shrink-0";
63
+
64
+ export function clampPanelWidth(width: number, minWidth: number, maxWidth: number): number {
65
+ return Math.min(maxWidth, Math.max(minWidth, Math.round(width)));
66
+ }
67
+
68
+ export function loadStoredPanelWidth(
69
+ storageKey: string,
70
+ defaultWidth: number,
71
+ minWidth: number,
72
+ maxWidth: number,
73
+ ): number {
74
+ if (typeof window === "undefined") return defaultWidth;
75
+ try {
76
+ const raw = window.localStorage.getItem(storageKey);
77
+ if (raw == null) return defaultWidth;
78
+ const parsed = Number(raw);
79
+ if (!Number.isFinite(parsed)) return defaultWidth;
80
+ return clampPanelWidth(parsed, minWidth, maxWidth);
81
+ } catch {
82
+ return defaultWidth;
83
+ }
84
+ }
85
+
86
+ export function saveStoredPanelWidth(storageKey: string, width: number): void {
87
+ if (typeof window === "undefined") return;
88
+ try {
89
+ window.localStorage.setItem(storageKey, String(width));
90
+ } catch {
91
+ // ignore quota / private mode
92
+ }
93
+ }
94
+
95
+ /** リサイズハンドルが属する論理エッジ(`dir` で物理左右が反転する) */
96
+ export type ResizeEdge = "inline-start" | "inline-end";
97
+
98
+ /**
99
+ * ポインタ移動量から新しいパネル幅を求める。
100
+ *
101
+ * 幅が広がる物理ドラッグ方向は「論理エッジ × 書字方向」で決まる:
102
+ * - inline-end エッジ(左サイドバー右端): LTR は右(+delta)で拡大、RTL は左(-delta)で拡大
103
+ * - inline-start エッジ(右パネル左端): LTR は左(-delta)で拡大、RTL は右(+delta)で拡大
104
+ * `isRtl` で物理符号を反転し、`dir="rtl"` の物理ドラッグ方向と整合させる(#1009)。
105
+ */
106
+ export function computeResizedWidth(
107
+ startWidth: number,
108
+ startPointerX: number,
109
+ currentPointerX: number,
110
+ edge: ResizeEdge,
111
+ minWidth: number,
112
+ maxWidth: number,
113
+ isRtl = false,
114
+ ): number {
115
+ const delta = currentPointerX - startPointerX;
116
+ const widenSign = (edge === "inline-end" ? 1 : -1) * (isRtl ? -1 : 1);
117
+ const next = startWidth + widenSign * delta;
118
+ return clampPanelWidth(next, minWidth, maxWidth);
119
+ }
120
+
121
+ /**
122
+ * 要素の書字方向が RTL かを判定する。
123
+ * 最も近い `[dir]` 祖先の明示値(大小文字非依存)を優先し、`rtl`/`ltr` 明示が無い場合
124
+ * (未指定 / `dir="auto"` / CSS `direction` のみ)は解決済み方向にフォールバックして CSS の
125
+ * 論理プロパティ反転と JS の符号反転を一致させる(#1009)。明示 dir を先に見るため jsdom でも判定可能。
126
+ */
127
+ export function isRtlContext(el: Element | null | undefined): boolean {
128
+ if (el == null) return false;
129
+ const explicit = el.closest("[dir]")?.getAttribute("dir")?.toLowerCase();
130
+ if (explicit === "rtl") return true;
131
+ if (explicit === "ltr") return false;
132
+ if (typeof window !== "undefined" && typeof window.getComputedStyle === "function") {
133
+ return window.getComputedStyle(el).direction === "rtl";
134
+ }
135
+ return false;
136
+ }
@@ -0,0 +1,17 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import { screen } from "@testing-library/react";
3
+ import { mockAuthContext } from "@/test/mocks";
4
+ import { renderWithProviders } from "@/test/render";
5
+ import LoginPage from "./LoginPage";
6
+
7
+ // user: null から isAuthenticated/canEdit が false に導出される(未認証状態)
8
+ vi.mock("@/contexts/AuthContext", () => ({
9
+ useAuth: () => mockAuthContext({ user: null }),
10
+ }));
11
+
12
+ describe("LoginPage", () => {
13
+ it("renders title", () => {
14
+ renderWithProviders(<LoginPage />);
15
+ expect(screen.getByText("GoDD Notes")).toBeTruthy();
16
+ });
17
+ });