@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
@@ -3,6 +3,7 @@ import { Link, useSearchParams } from "react-router-dom";
3
3
  import { useAuth } from "@/contexts/AuthContext";
4
4
  import { useToast } from "@/contexts/ToastContext";
5
5
  import { useEditSession } from "@/hooks/useEditSession";
6
+ import { usePeriodicSync } from "@/hooks/usePeriodicSync";
6
7
  import { useAutoSave } from "@/hooks/useAutoSave";
7
8
  import {
8
9
  apiFetch,
@@ -29,19 +30,49 @@ import { useCollaboration } from "@/hooks/useCollaboration";
29
30
  import { useComments } from "@/hooks/useComments";
30
31
  import CollabStatus from "@/components/CollabStatus";
31
32
  import CommentPanel from "@/components/CommentPanel";
33
+ import CommentSelectionPopover from "@/components/CommentSelectionPopover";
34
+ import type { CommentAnchor } from "@/lib/commentAnchor";
35
+ import { getWindowSelectionAnchor } from "@/lib/commentAnchor";
32
36
  import EditToolbar from "@/components/EditToolbar";
33
37
  import FileInfoPanel from "@/components/FileInfoPanel";
38
+ import PanelResizeHandle from "@/components/PanelResizeHandle";
34
39
  import { FilePlus, FolderPlus, ChevronsDownUp, Users, Settings, LogOut, X, GitBranch, Menu, CircleDot, FolderTree, Plus, ArrowLeft, MessageSquare, Info } from "lucide-react";
35
40
  import TreeNode from "@/components/TreeNode";
36
41
  import FileContentView from "@/components/FileContentView";
37
42
  import BranchSelectorModal from "@/components/BranchSelectorModal";
38
43
  import IssueList from "@/components/IssueList";
44
+ import type { IssueDetail, IssueSummary } from "@/lib/api";
39
45
  import type { TreeEntry, FileContent, ApiState, FileStyles } from "@/types";
40
46
  import { getFileExt } from "@/types";
41
47
  import { insertChildEntry, moveEntry, removeEntry } from "@/lib/tree-utils";
42
48
  import { createdFileToContent, createdFileToTreeEntry } from "@/lib/created-file";
43
49
  import { DEFAULT_BASE_REF, hasContentDiff, resolveBranchLabels, resolveOriginalContent } from "@/lib/branch-diff";
44
50
  import { clearDraft, loadDraft, saveDraft } from "@/lib/draft-storage";
51
+ import {
52
+ buildMainPageSearchParams,
53
+ parseIssueSearchParam,
54
+ type IssueUrlSelection,
55
+ } from "@/lib/issue-url-sync";
56
+ import {
57
+ issueDetailToSummary,
58
+ shouldClearOptimisticForSelection,
59
+ type OptimisticSettleMode,
60
+ } from "@/lib/issue-list-utils";
61
+ import {
62
+ LEFT_SIDEBAR_LAYOUT,
63
+ RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS,
64
+ RESIZABLE_RIGHT_PANEL_ASIDE_CLASS,
65
+ RIGHT_PANEL_LAYOUT,
66
+ panelResizeWithoutHandle,
67
+ resolveMdUpPanelResize,
68
+ } from "@/lib/resizable-panel";
69
+ import {
70
+ GITHUB_SYNC_POLL_INTERVAL_MS,
71
+ shouldDispatchIssueListBackgroundSync,
72
+ shouldSyncGithub,
73
+ } from "@/lib/github-sync";
74
+ import { useResizablePanelWidth } from "@/hooks/useResizablePanelWidth";
75
+ import { resolvePanelWidthStyle, useMdUpLayout } from "@/hooks/useMdUpLayout";
45
76
 
46
77
  const MarkdownEditor = lazy(() => import("@/components/MarkdownEditor"));
47
78
  const SpreadsheetEditor = lazy(() => import("@/components/SpreadsheetEditor"));
@@ -64,21 +95,32 @@ async function fetchFileContent(path: string, ref: string | null, signal: AbortS
64
95
  export default function MainPage() {
65
96
  const { user, canEdit, logout } = useAuth();
66
97
  const { showToast } = useToast();
67
- const editSession = useEditSession();
68
98
 
69
99
  const [searchParams, setSearchParams] = useSearchParams();
70
100
 
71
101
  const [tree, setTree] = useState<TreeEntry[]>([]);
72
102
  const [apiState, setApiState] = useState<ApiState>("loading");
103
+ const githubSyncEnabled = shouldSyncGithub(apiState);
73
104
  const [errorMsg, setErrorMsg] = useState("");
74
105
  const [selectedFile, setSelectedFile] = useState<string | null>(
75
106
  () => searchParams.get("file"),
76
107
  );
108
+ const [sidebarTab, setSidebarTab] = useState<SidebarTab>(() => {
109
+ if (searchParams.get("file")) return "files";
110
+ return parseIssueSearchParam(searchParams.get("issue")) !== null ? "issues" : "files";
111
+ });
112
+ const [selectedIssue, setSelectedIssue] = useState<number | "new" | null>(() =>
113
+ searchParams.get("file") ? null : parseIssueSearchParam(searchParams.get("issue")),
114
+ );
115
+ // IssueList が自前の periodic sync を実行している状態(Issues タブ表示 + live)。
116
+ // 詳細 poll 起因の一覧背景再取得の重複抑制判定と IssueList の syncEnabled で単一ソース化する(#932)
117
+ const issueListPeriodicSyncing = githubSyncEnabled && sidebarTab === "issues";
77
118
  const [fileContent, setFileContent] = useState<FileContent | null>(null);
78
119
  const [baseFileContent, setBaseFileContent] = useState<FileContent | null>(null);
79
120
  const [baseFileError, setBaseFileError] = useState<string | null>(null);
80
121
  const [fileLoading, setFileLoading] = useState(false);
81
122
  const [editing, setEditing] = useState(false);
123
+ const editSession = useEditSession({ syncEnabled: githubSyncEnabled && editing });
82
124
  const [treeFilter, setTreeFilter] = useState("");
83
125
  const [collapseKey, setCollapseKey] = useState(0);
84
126
  const [activeDir, setActiveDir] = useState("docs");
@@ -138,20 +180,55 @@ export default function MainPage() {
138
180
 
139
181
  const treeAbortRef = useRef<AbortController | null>(null);
140
182
  const treeSyncTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
141
-
142
- const loadBranches = useCallback(() => {
143
- setBranchesLoading(true);
144
- fetchProposalsApi()
183
+ // ブランチ選択モーダルの proposals 取得: foreground(手動 refresh・初回)と background poll で
184
+ // seq を分離し、並走時に古い応答が新しい proposals を上書きしないようにする(#936、#934 と同型)
185
+ const loadBranchesSeq = useRef(0);
186
+ const pollBranchesSeq = useRef(0);
187
+
188
+ // foreground 取得開始時は poll seq も進め、in-flight の background poll を無効化する
189
+ const beginLoadBranches = useCallback(() => {
190
+ loadBranchesSeq.current += 1;
191
+ pollBranchesSeq.current += 1;
192
+ return loadBranchesSeq.current;
193
+ }, []);
194
+
195
+ // background poll は poll seq のみ進め、in-flight の foreground 取得は無効化しない
196
+ const beginPollBranches = useCallback(() => {
197
+ pollBranchesSeq.current += 1;
198
+ return pollBranchesSeq.current;
199
+ }, []);
200
+
201
+ const loadBranches = useCallback((options?: { background?: boolean }) => {
202
+ const background = options?.background ?? false;
203
+ const requestId = background ? beginPollBranches() : beginLoadBranches();
204
+ const isStale = () =>
205
+ background
206
+ ? requestId !== pollBranchesSeq.current
207
+ : requestId !== loadBranchesSeq.current;
208
+ if (!background) {
209
+ setBranchesLoading(true);
210
+ }
211
+ // Promise を返し、usePeriodicSync の in-flight ガード(#937)で並列 poll を防ぐ
212
+ return fetchProposalsApi()
145
213
  .then((data) => {
214
+ if (isStale()) return;
146
215
  const openBranches = data.filter((p) => p.state === "open" && !p.merged);
147
216
  setBranches(openBranches);
148
217
  })
149
218
  .catch((err) => {
150
- setBranches([]);
151
- showToast(getErrorMessage(err, "ブランチ一覧の取得に失敗しました"), "error");
219
+ if (isStale()) return;
220
+ if (!background) {
221
+ setBranches([]);
222
+ showToast(getErrorMessage(err, "ブランチ一覧の取得に失敗しました"), "error");
223
+ }
152
224
  })
153
- .finally(() => setBranchesLoading(false));
154
- }, [showToast]);
225
+ .finally(() => {
226
+ if (isStale()) return;
227
+ if (!background) {
228
+ setBranchesLoading(false);
229
+ }
230
+ });
231
+ }, [showToast, beginLoadBranches, beginPollBranches]);
155
232
 
156
233
  const loadTree = useCallback((ref?: string) => {
157
234
  treeAbortRef.current?.abort();
@@ -190,7 +267,17 @@ export default function MainPage() {
190
267
  });
191
268
  }, [showToast]);
192
269
 
193
- const initialFileLoaded = useRef(false);
270
+ const initialDeepLinkLoaded = useRef(false);
271
+
272
+ const syncIssueToUrl = useCallback(
273
+ (issue: IssueUrlSelection) => {
274
+ setSearchParams(
275
+ buildMainPageSearchParams({ issue, preserve: searchParams }),
276
+ { replace: true },
277
+ );
278
+ },
279
+ [searchParams, setSearchParams],
280
+ );
194
281
 
195
282
  useEffect(() => {
196
283
  loadTree(viewingRef ?? undefined);
@@ -282,6 +369,7 @@ export default function MainPage() {
282
369
  setBaseFileContent(null);
283
370
  setBaseFileError(null);
284
371
  setEditing(false);
372
+ setShowOriginal(false);
285
373
  setShowDiff(false);
286
374
  setEditedContent(null);
287
375
  autoSave.reset();
@@ -297,10 +385,14 @@ export default function MainPage() {
297
385
  autoSave.reset();
298
386
  pendingStylesRef.current = null;
299
387
  setSelectedFile(path);
388
+ setSelectedIssue(null);
300
389
  setBaseFileContent(null);
301
390
  setBaseFileError(null);
302
391
  setActiveDir(path.substring(0, path.lastIndexOf("/")) || "docs");
303
- setSearchParams({ file: path }, { replace: true });
392
+ setSearchParams(
393
+ buildMainPageSearchParams({ file: path, preserve: searchParams }),
394
+ { replace: true },
395
+ );
304
396
  setFileLoading(true);
305
397
  setEditing(false);
306
398
  setShowOriginal(false);
@@ -343,17 +435,45 @@ export default function MainPage() {
343
435
  .finally(() => {
344
436
  if (!controller.signal.aborted) setFileLoading(false);
345
437
  });
346
- }, [autoSave, editSession.session?.branchName, showToast, setSearchParams, viewingRef]);
438
+ }, [autoSave, editSession.session?.branchName, showToast, setSearchParams, searchParams, viewingRef]);
347
439
 
348
440
  useEffect(() => {
349
- if (initialFileLoaded.current) return;
441
+ if (initialDeepLinkLoaded.current) return;
350
442
  if (apiState !== "ready" && apiState !== "mock") return;
443
+
351
444
  const fileParam = searchParams.get("file");
445
+ const issueParam = parseIssueSearchParam(searchParams.get("issue"));
446
+
352
447
  if (fileParam && !fileContent) {
353
- initialFileLoaded.current = true;
448
+ initialDeepLinkLoaded.current = true;
354
449
  handleFileSelect(fileParam);
450
+ return;
355
451
  }
356
- }, [apiState, searchParams, fileContent, handleFileSelect]);
452
+
453
+ if (issueParam !== null && selectedIssue === null && !selectedFile) {
454
+ initialDeepLinkLoaded.current = true;
455
+ setSidebarTab("issues");
456
+ setSelectedIssue(issueParam);
457
+ }
458
+ }, [apiState, searchParams, fileContent, selectedIssue, selectedFile, handleFileSelect]);
459
+
460
+ useEffect(() => {
461
+ if (searchParams.get("file")) return;
462
+
463
+ const issueParam = parseIssueSearchParam(searchParams.get("issue"));
464
+ if (issueParam !== null) {
465
+ setSidebarTab("issues");
466
+ setSelectedIssue((current) => (current === issueParam ? current : issueParam));
467
+ setSelectedFile(null);
468
+ setFileContent(null);
469
+ setEditing(false);
470
+ return;
471
+ }
472
+
473
+ if (!searchParams.get("issue")) {
474
+ setSelectedIssue((current) => (current === null ? current : null));
475
+ }
476
+ }, [searchParams]);
357
477
 
358
478
  const handleSave = useCallback(
359
479
  async (newContent: string, styles?: FileStyles | null) => {
@@ -427,7 +547,10 @@ export default function MainPage() {
427
547
  setBaseFileContent(createdFileToContent(created));
428
548
  setBaseFileError(null);
429
549
  setActiveDir(parentPath);
430
- setSearchParams({ file: created.path }, { replace: true });
550
+ setSearchParams(
551
+ buildMainPageSearchParams({ file: created.path, preserve: searchParams }),
552
+ { replace: true },
553
+ );
431
554
  setFileLoading(false);
432
555
  setEditing(false);
433
556
  setShowOriginal(false);
@@ -441,7 +564,7 @@ export default function MainPage() {
441
564
  showToast(`作成に失敗しました: ${getErrorMessage(e)}`, "error");
442
565
  }
443
566
  },
444
- [dialog, showToast, autoSave, setSearchParams, loadTree],
567
+ [dialog, showToast, autoSave, setSearchParams, searchParams, loadTree],
445
568
  );
446
569
 
447
570
  const handleCreateFolder = useCallback(
@@ -617,14 +740,195 @@ export default function MainPage() {
617
740
  const diffButtonLabel = baseFileError ? "差分確認不可" : contentHasDiff ? "差分あり" : "差分";
618
741
  const [showCommentPanel, setShowCommentPanel] = useState(false);
619
742
  const [showFileInfoPanel, setShowFileInfoPanel] = useState(false);
743
+ const [commentAnchor, setCommentAnchor] = useState<CommentAnchor | null>(null);
744
+ const [viewSelectionPopover, setViewSelectionPopover] = useState<{
745
+ top: number;
746
+ left: number;
747
+ anchor: CommentAnchor;
748
+ } | null>(null);
620
749
  const comments = useComments(selectedFile);
621
750
 
751
+ const commentHighlights = useMemo(
752
+ () =>
753
+ comments.comments
754
+ .filter(
755
+ (c) =>
756
+ c.parent_id === null &&
757
+ c.anchor_text &&
758
+ c.position_from != null &&
759
+ c.position_to != null &&
760
+ c.resolved_at === null,
761
+ )
762
+ .map((c) => ({
763
+ anchor_text: c.anchor_text as string,
764
+ position_from: c.position_from as number,
765
+ position_to: c.position_to as number,
766
+ })),
767
+ [comments.comments],
768
+ );
769
+
770
+ const handleCommentAnchor = useCallback((anchor: CommentAnchor) => {
771
+ setCommentAnchor(anchor);
772
+ setShowCommentPanel(true);
773
+ setShowFileInfoPanel(false);
774
+ setViewSelectionPopover(null);
775
+ }, []);
776
+
777
+ const handleViewModeSelection = useCallback(() => {
778
+ if (!fileContent) return;
779
+ const ext = getFileExt(fileContent.path);
780
+ if (ext !== "md" && ext !== "mdx") return;
781
+
782
+ const selection = window.getSelection();
783
+ if (!selection || selection.isCollapsed || selection.rangeCount === 0) {
784
+ setViewSelectionPopover(null);
785
+ return;
786
+ }
787
+
788
+ const anchor = getWindowSelectionAnchor(fileContent.content);
789
+ if (!anchor) {
790
+ setViewSelectionPopover(null);
791
+ return;
792
+ }
793
+
794
+ const rect = selection.getRangeAt(0).getBoundingClientRect();
795
+ setViewSelectionPopover({
796
+ top: rect.top,
797
+ left: rect.left + rect.width / 2,
798
+ anchor,
799
+ });
800
+ }, [fileContent]);
801
+
802
+ useEffect(() => {
803
+ setCommentAnchor(null);
804
+ setViewSelectionPopover(null);
805
+ }, [selectedFile, editing]);
806
+
622
807
  const [sidebarOpen, setSidebarOpen] = useState(false);
623
- const [sidebarTab, setSidebarTab] = useState<SidebarTab>("files");
624
- const [selectedIssue, setSelectedIssue] = useState<number | "new" | null>(null);
808
+ const leftSidebarLayout = useResizablePanelWidth({
809
+ ...LEFT_SIDEBAR_LAYOUT,
810
+ edge: "inline-end",
811
+ });
812
+ const rightPanelLayout = useResizablePanelWidth({
813
+ ...RIGHT_PANEL_LAYOUT,
814
+ edge: "inline-start",
815
+ });
816
+ const mdUpLayout = useMdUpLayout();
817
+ const leftPanelResize = resolveMdUpPanelResize(
818
+ mdUpLayout,
819
+ leftSidebarLayout.width,
820
+ LEFT_SIDEBAR_LAYOUT.minWidth,
821
+ LEFT_SIDEBAR_LAYOUT.maxWidth,
822
+ leftSidebarLayout.onResizePointerDown,
823
+ leftSidebarLayout.applyWidth,
824
+ );
825
+ const rightPanelResize = resolveMdUpPanelResize(
826
+ mdUpLayout,
827
+ rightPanelLayout.width,
828
+ RIGHT_PANEL_LAYOUT.minWidth,
829
+ RIGHT_PANEL_LAYOUT.maxWidth,
830
+ rightPanelLayout.onResizePointerDown,
831
+ rightPanelLayout.applyWidth,
832
+ );
833
+ const secondaryRightPanelResize =
834
+ rightPanelResize && showOriginal && fileContent
835
+ ? panelResizeWithoutHandle(rightPanelResize)
836
+ : rightPanelResize;
837
+ const toggleFileInfoPanel = useCallback(() => {
838
+ setShowFileInfoPanel((prev) => {
839
+ const next = !prev;
840
+ if (next) {
841
+ setShowCommentPanel(false);
842
+ setCommentAnchor(null);
843
+ }
844
+ return next;
845
+ });
846
+ }, []);
847
+ const toggleCommentPanel = useCallback(() => {
848
+ setShowCommentPanel((prev) => {
849
+ const next = !prev;
850
+ if (next) {
851
+ setShowFileInfoPanel(false);
852
+ } else {
853
+ setCommentAnchor(null);
854
+ }
855
+ return next;
856
+ });
857
+ }, []);
625
858
  const [issueListReloadTrigger, setIssueListReloadTrigger] = useState(0);
859
+ const [issueListBackgroundSyncTrigger, setIssueListBackgroundSyncTrigger] = useState(0);
860
+ const [optimisticIssue, setOptimisticIssue] = useState<IssueSummary | null>(null);
861
+ const [optimisticSettleMode, setOptimisticSettleMode] = useState<OptimisticSettleMode>("presence");
626
862
  const [newIssueKey, setNewIssueKey] = useState(0);
627
863
 
864
+ const handleIssueCreated = useCallback(
865
+ (issue: IssueSummary) => {
866
+ setSidebarTab("issues");
867
+ setSelectedIssue(issue.number);
868
+ syncIssueToUrl(issue.number);
869
+ // 新規作成は一覧未出現 → 番号出現で解除する presence モード(#932)
870
+ setOptimisticSettleMode("presence");
871
+ setOptimisticIssue(issue);
872
+ setIssueListReloadTrigger((key) => key + 1);
873
+ },
874
+ [syncIssueToUrl],
875
+ );
876
+
877
+ const handleOptimisticIssueSettled = useCallback((issueNumber: number) => {
878
+ setOptimisticIssue((current) => (current?.number === issueNumber ? null : current));
879
+ }, []);
880
+
881
+ const handleLabelsMutated = useCallback(() => {
882
+ setIssueListReloadTrigger((key) => key + 1);
883
+ }, []);
884
+
885
+ const handleIssueExternalChange = useCallback(
886
+ (issue: IssueDetail, options?: { background?: boolean }) => {
887
+ if (options?.background) {
888
+ // IssueList が periodic sync 中は一覧全体を毎周期再取得するため、ここでの追加再取得は
889
+ // 同一周期内の GET /api/issues 二重 dispatch となる。重複時は抑制する(#932)
890
+ if (shouldDispatchIssueListBackgroundSync({ issueListSyncing: issueListPeriodicSyncing })) {
891
+ setIssueListBackgroundSyncTrigger((key) => key + 1);
892
+ } else {
893
+ // 背景再取得を抑制している間は、詳細 poll が取得済みの IssueDetail を optimistic merge に
894
+ // 渡し、追加 GET /api/issues なしで選択中 Issue の一覧行を即時更新する(#945)。
895
+ // 次の periodic tick が実体を取り込むと snapshot モードの判定で onOptimisticSettled が解除する。
896
+ setOptimisticSettleMode("snapshot");
897
+ setOptimisticIssue(issueDetailToSummary(issue));
898
+ }
899
+ } else {
900
+ setIssueListReloadTrigger((key) => key + 1);
901
+ }
902
+ },
903
+ [issueListPeriodicSyncing],
904
+ );
905
+
906
+ // snapshot optimistic は選択中 Issue 専用。別 Issue へ移動したら破棄し、古い行は
907
+ // IssueList 自前の periodic sync に委ねる(#945 second-pass: スロットの固着回避)
908
+ useEffect(() => {
909
+ if (shouldClearOptimisticForSelection(optimisticIssue, optimisticSettleMode, selectedIssue)) {
910
+ setOptimisticIssue(null);
911
+ }
912
+ }, [optimisticIssue, optimisticSettleMode, selectedIssue]);
913
+
914
+ usePeriodicSync({
915
+ enabled: githubSyncEnabled && branchModalOpen,
916
+ intervalMs: GITHUB_SYNC_POLL_INTERVAL_MS,
917
+ onSync: () => loadBranches({ background: true }),
918
+ });
919
+
920
+ const openSidebar = useCallback(
921
+ (tab?: SidebarTab) => {
922
+ if (tab) {
923
+ setSidebarTab(tab);
924
+ } else if (selectedIssue != null && !selectedFile) {
925
+ setSidebarTab("issues");
926
+ }
927
+ setSidebarOpen(true);
928
+ },
929
+ [selectedIssue, selectedFile],
930
+ );
931
+
628
932
  const handleFileSelectResponsive = useCallback(
629
933
  (path: string) => {
630
934
  handleFileSelect(path);
@@ -645,14 +949,11 @@ export default function MainPage() {
645
949
  />
646
950
  )}
647
951
 
648
- {/* Sidebar — fixed drawer on mobile, static on md+ */}
952
+ {/* Sidebar — fixed drawer on mobile, relative on md+ */}
649
953
  <aside
650
- className={`
651
- fixed inset-y-0 left-0 z-40 w-64 bg-gray-800 border-r border-gray-700 flex flex-col
652
- transform transition-transform duration-200 ease-in-out
653
- md:static md:translate-x-0
654
- ${sidebarOpen ? "translate-x-0" : "-translate-x-full"}
655
- `}
954
+ data-testid="left-sidebar"
955
+ className={`${RESIZABLE_LEFT_SIDEBAR_ASIDE_CLASS} ${sidebarOpen ? "translate-x-0" : "-translate-x-full max-md:rtl:translate-x-full"}`}
956
+ style={resolvePanelWidthStyle(leftSidebarLayout.width, mdUpLayout)}
656
957
  >
657
958
  <div className="p-3 border-b border-gray-700 flex items-center justify-between">
658
959
  <div className="flex items-center gap-1">
@@ -703,7 +1004,7 @@ export default function MainPage() {
703
1004
  <button
704
1005
  type="button"
705
1006
  onClick={() => setSidebarTab("files")}
706
- className={`flex-1 flex items-center justify-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors ${
1007
+ className={`flex-1 flex items-center justify-center gap-1.5 px-2 py-2 sm:py-2 min-h-11 sm:min-h-0 touch-manipulation text-xs font-medium transition-colors ${
707
1008
  sidebarTab === "files"
708
1009
  ? "text-blue-400 border-b-2 border-blue-400"
709
1010
  : "text-gray-500 hover:text-gray-300"
@@ -715,7 +1016,7 @@ export default function MainPage() {
715
1016
  <button
716
1017
  type="button"
717
1018
  onClick={() => setSidebarTab("issues")}
718
- className={`flex-1 flex items-center justify-center gap-1.5 px-2 py-2 text-xs font-medium transition-colors ${
1019
+ className={`flex-1 flex items-center justify-center gap-1.5 px-2 py-2 sm:py-2 min-h-11 sm:min-h-0 touch-manipulation text-xs font-medium transition-colors ${
719
1020
  sidebarTab === "issues"
720
1021
  ? "text-blue-400 border-b-2 border-blue-400"
721
1022
  : "text-gray-500 hover:text-gray-300"
@@ -843,9 +1144,9 @@ export default function MainPage() {
843
1144
  )}
844
1145
 
845
1146
  {sidebarTab === "issues" && (
846
- <>
1147
+ <div className="flex-1 min-h-0 flex flex-col">
847
1148
  {canEdit && (
848
- <div className="px-2 pt-2">
1149
+ <div className="px-2 pt-2 shrink-0">
849
1150
  <button
850
1151
  type="button"
851
1152
  onClick={() => {
@@ -853,9 +1154,10 @@ export default function MainPage() {
853
1154
  setNewIssueKey((k) => k + 1);
854
1155
  setSelectedFile(null);
855
1156
  setFileContent(null);
1157
+ syncIssueToUrl("new");
856
1158
  setSidebarOpen(false);
857
1159
  }}
858
- className="w-full px-2 py-1.5 rounded flex items-center justify-center gap-1.5 text-xs text-green-300 bg-green-900/20 border border-green-700/40 hover:bg-green-900/40 transition-colors"
1160
+ className="w-full px-2 py-2.5 sm:py-1.5 min-h-11 sm:min-h-0 touch-manipulation rounded flex items-center justify-center gap-1.5 text-xs text-green-300 bg-green-900/20 border border-green-700/40 hover:bg-green-900/40 transition-colors"
859
1161
  >
860
1162
  <Plus className="w-3.5 h-3.5" />
861
1163
  新規 Issue
@@ -865,15 +1167,32 @@ export default function MainPage() {
865
1167
  <IssueList
866
1168
  selectedIssue={typeof selectedIssue === "number" ? selectedIssue : null}
867
1169
  reloadTrigger={issueListReloadTrigger}
1170
+ backgroundSyncTrigger={issueListBackgroundSyncTrigger}
1171
+ optimisticIssue={optimisticIssue}
1172
+ optimisticSettleMode={optimisticSettleMode}
1173
+ onOptimisticSettled={handleOptimisticIssueSettled}
1174
+ syncEnabled={issueListPeriodicSyncing}
868
1175
  onSelect={(num) => {
869
1176
  setSelectedIssue(num);
870
1177
  setSelectedFile(null);
871
1178
  setFileContent(null);
872
1179
  setEditing(false);
1180
+ syncIssueToUrl(num);
873
1181
  setSidebarOpen(false);
874
1182
  }}
875
1183
  />
876
- </>
1184
+ </div>
1185
+ )}
1186
+ {leftPanelResize && (
1187
+ <PanelResizeHandle
1188
+ edge="inline-end"
1189
+ onPointerDown={leftPanelResize.onPointerDown}
1190
+ width={leftPanelResize.width}
1191
+ minWidth={leftPanelResize.minWidth}
1192
+ maxWidth={leftPanelResize.maxWidth}
1193
+ onWidthChange={leftPanelResize.onWidthChange}
1194
+ ariaLabel="左サイドバーの幅を変更"
1195
+ />
877
1196
  )}
878
1197
  </aside>
879
1198
 
@@ -883,12 +1202,18 @@ export default function MainPage() {
883
1202
  <div className="border-b border-gray-700 px-2 sm:px-4 py-2 bg-gray-800 flex flex-wrap items-center gap-1.5 sm:gap-2">
884
1203
  <button
885
1204
  type="button"
886
- onClick={() => setSidebarOpen(true)}
887
- className="text-gray-400 hover:text-gray-200 md:hidden shrink-0"
888
- title="ファイルツリー"
889
- aria-label="ファイルツリー"
1205
+ onClick={() => openSidebar(selectedIssue != null && !selectedFile ? "issues" : undefined)}
1206
+ className="text-gray-400 hover:text-gray-200 md:hidden shrink-0 min-h-11 min-w-11 touch-manipulation flex items-center justify-center rounded hover:bg-gray-700/60"
1207
+ title={selectedIssue != null && !selectedFile ? "Issue 一覧" : "サイドバー"}
1208
+ aria-label={
1209
+ selectedIssue != null && !selectedFile ? "Issue 一覧を開く" : "サイドバーを開く"
1210
+ }
890
1211
  >
891
- <Menu className="w-4 h-4" />
1212
+ {selectedIssue != null && !selectedFile ? (
1213
+ <ArrowLeft className="w-4 h-4" />
1214
+ ) : (
1215
+ <Menu className="w-4 h-4" />
1216
+ )}
892
1217
  </button>
893
1218
  <span className="text-sm text-gray-400 flex-1 truncate">
894
1219
  {selectedFile
@@ -935,7 +1260,7 @@ export default function MainPage() {
935
1260
  {fileContent && (
936
1261
  <button
937
1262
  type="button"
938
- onClick={() => setShowFileInfoPanel((v) => !v)}
1263
+ onClick={toggleFileInfoPanel}
939
1264
  className={`flex items-center gap-1 px-3 py-1 rounded text-sm transition-colors ${
940
1265
  showFileInfoPanel
941
1266
  ? "bg-gray-600 text-white"
@@ -949,7 +1274,7 @@ export default function MainPage() {
949
1274
  {fileContent && (
950
1275
  <button
951
1276
  type="button"
952
- onClick={() => setShowCommentPanel((v) => !v)}
1277
+ onClick={toggleCommentPanel}
953
1278
  className={`flex items-center gap-1 px-3 py-1 rounded text-sm transition-colors ${
954
1279
  showCommentPanel
955
1280
  ? "bg-gray-600 text-white"
@@ -982,21 +1307,6 @@ export default function MainPage() {
982
1307
  error={editSession.error}
983
1308
  canEdit={canEdit}
984
1309
  onCreatePr={editSession.createPr}
985
- onSubmitPr={editSession.submitPr}
986
- onConvertToDraft={editSession.convertToDraft}
987
- onDiscardChanges={async () => {
988
- const ok = await editSession.discardChanges();
989
- if (ok) {
990
- setEditing(false);
991
- setSelectedFile(null);
992
- setFileContent(null);
993
- setBaseFileContent(null);
994
- setBaseFileError(null);
995
- setSearchParams({}, { replace: true });
996
- autoSave.reset();
997
- }
998
- return ok;
999
- }}
1000
1310
  onClearError={editSession.clearError}
1001
1311
  />
1002
1312
  )}
@@ -1017,11 +1327,14 @@ export default function MainPage() {
1017
1327
  <IssueDetailView
1018
1328
  key={selectedIssue === "new" ? `new-${newIssueKey}` : String(selectedIssue)}
1019
1329
  issueNumber={selectedIssue}
1020
- onCreated={(num) => {
1021
- setSelectedIssue(num);
1022
- setIssueListReloadTrigger((k) => k + 1);
1330
+ onCreated={handleIssueCreated}
1331
+ onLabelsMutated={handleLabelsMutated}
1332
+ onExternalChange={handleIssueExternalChange}
1333
+ syncEnabled={githubSyncEnabled}
1334
+ onClose={() => {
1335
+ setSelectedIssue(null);
1336
+ syncIssueToUrl(null);
1023
1337
  }}
1024
- onClose={() => setSelectedIssue(null)}
1025
1338
  />
1026
1339
  </Suspense>
1027
1340
  )}
@@ -1040,7 +1353,7 @@ export default function MainPage() {
1040
1353
  )}
1041
1354
 
1042
1355
  {fileContent && !fileLoading && !editing && (
1043
- <div className="p-3 sm:p-6">
1356
+ <div className="p-3 sm:p-6" onMouseUp={handleViewModeSelection}>
1044
1357
  <FileContentView path={fileContent.path} content={fileContent.content} encoding={fileContent.encoding} />
1045
1358
  </div>
1046
1359
  )}
@@ -1073,6 +1386,8 @@ export default function MainPage() {
1073
1386
  ydoc={collab.ydoc}
1074
1387
  provider={collab.provider}
1075
1388
  collabUser={collabUserStyle}
1389
+ onCommentAnchor={handleCommentAnchor}
1390
+ commentHighlights={commentHighlights}
1076
1391
  />
1077
1392
  )}
1078
1393
  {(ext === "csv" || ext === "xlsx" || ext === "xls") && (
@@ -1112,7 +1427,21 @@ export default function MainPage() {
1112
1427
 
1113
1428
  {/* Original content reference panel */}
1114
1429
  {showOriginal && fileContent && (
1115
- <aside className="absolute inset-0 md:static md:w-96 z-20 border-l border-gray-700 bg-gray-800 flex flex-col shrink-0">
1430
+ <aside
1431
+ className={RESIZABLE_RIGHT_PANEL_ASIDE_CLASS}
1432
+ style={resolvePanelWidthStyle(rightPanelLayout.width, mdUpLayout)}
1433
+ >
1434
+ {rightPanelResize && (
1435
+ <PanelResizeHandle
1436
+ edge="inline-start"
1437
+ onPointerDown={rightPanelResize.onPointerDown}
1438
+ width={rightPanelResize.width}
1439
+ minWidth={rightPanelResize.minWidth}
1440
+ maxWidth={rightPanelResize.maxWidth}
1441
+ onWidthChange={rightPanelResize.onWidthChange}
1442
+ ariaLabel="参照パネルの幅を変更"
1443
+ />
1444
+ )}
1116
1445
  <div className="flex items-center justify-between px-3 py-2 border-b border-gray-700">
1117
1446
  <h3 className="text-sm font-semibold text-gray-300">{baseBranch} のソース</h3>
1118
1447
  <button
@@ -1147,7 +1476,13 @@ export default function MainPage() {
1147
1476
  filePath={selectedFile}
1148
1477
  currentUser={user}
1149
1478
  comments={comments}
1150
- onClose={() => setShowCommentPanel(false)}
1479
+ pendingAnchor={commentAnchor}
1480
+ onClearAnchor={() => setCommentAnchor(null)}
1481
+ panelResize={secondaryRightPanelResize}
1482
+ onClose={() => {
1483
+ setShowCommentPanel(false);
1484
+ setCommentAnchor(null);
1485
+ }}
1151
1486
  />
1152
1487
  )}
1153
1488
 
@@ -1156,6 +1491,8 @@ export default function MainPage() {
1156
1491
  <FileInfoPanel
1157
1492
  fileContent={fileContent}
1158
1493
  collabUsers={collab.users}
1494
+ panelResize={secondaryRightPanelResize}
1495
+ syncEnabled={githubSyncEnabled}
1159
1496
  onClose={() => setShowFileInfoPanel(false)}
1160
1497
  />
1161
1498
  )}
@@ -1213,6 +1550,14 @@ export default function MainPage() {
1213
1550
  onSelect={handleBranchSelect}
1214
1551
  onRefresh={loadBranches}
1215
1552
  />
1553
+
1554
+ {viewSelectionPopover && !editing && (
1555
+ <CommentSelectionPopover
1556
+ top={viewSelectionPopover.top}
1557
+ left={viewSelectionPopover.left}
1558
+ onAddComment={() => handleCommentAnchor(viewSelectionPopover.anchor)}
1559
+ />
1560
+ )}
1216
1561
  </div>
1217
1562
  );
1218
1563
  }