@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.
- package/README.md +89 -94
- package/dist/godd.cjs +349 -233
- package/dist/godd.js +2193 -838
- package/dist/index.js +152 -122
- package/{notes-api → notes/api}/.env.example +6 -1
- package/{notes-api → notes/api}/README.md +6 -5
- package/{notes-api → notes/api}/app/main.py +29 -2
- package/{notes-api → notes/api}/app/routers/auth.py +6 -1
- package/{notes-api → notes/api}/app/routers/issues.py +157 -1
- package/{notes-api → notes/api}/app/routers/pr.py +2 -2
- package/{notes-api → notes/api}/app/services/github_app_token.py +32 -1
- package/{notes-api → notes/api}/app/services/github_issues.py +101 -11
- package/{notes-api → notes/api}/app/services/github_pr.py +6 -1
- package/{notes-api → notes/api}/tests/test_github_app_token.py +49 -1
- package/{notes-api → notes/api}/tests/test_github_service.py +42 -0
- package/notes/api/tests/test_health.py +52 -0
- package/{notes-api → notes/api}/tests/test_issues.py +216 -0
- package/{notes-api → notes/api}/tests/test_pr.py +24 -0
- package/{notes-api → notes/api}/tests/test_pr_chain.py +19 -0
- package/{notes-api → notes/api}/uv.lock +178 -2
- package/notes/app/DESIGN.md +273 -0
- package/{notes-app → notes/app}/README.md +4 -1
- package/notes/app/docker/Dockerfile.e2e +19 -0
- package/notes/app/docker/Dockerfile.prod +15 -0
- package/notes/app/docker/Dockerfile.test +17 -0
- package/notes/app/e2e/file-tree.spec.ts +26 -0
- package/notes/app/e2e/file-view.spec.ts +25 -0
- package/notes/app/e2e/fixtures.ts +28 -0
- package/{notes-app → notes/app}/e2e/global-setup.ts +5 -7
- package/notes/app/e2e/login.spec.ts +25 -0
- package/notes/app/e2e/pages/login-page.ts +29 -0
- package/notes/app/e2e/pages/main-page.ts +29 -0
- package/notes/app/e2e/panel-resize-handle.spec.ts +213 -0
- package/{notes-app → notes/app}/eslint.config.js +9 -0
- package/{notes-app → notes/app}/package.json +0 -3
- package/{notes-app → notes/app}/pnpm-workspace.yaml +1 -0
- package/notes/app/src/components/CommentPanel.test.tsx +194 -0
- package/{notes-app → notes/app}/src/components/CommentPanel.tsx +119 -10
- package/notes/app/src/components/CommentSelectionPopover.tsx +26 -0
- package/notes/app/src/components/EditToolbar.test.tsx +102 -0
- package/{notes-app → notes/app}/src/components/EditToolbar.tsx +5 -54
- package/notes/app/src/components/FileInfoPanel.branch-tab.test.tsx +250 -0
- package/notes/app/src/components/FileInfoPanel.history-tab.test.tsx +146 -0
- package/notes/app/src/components/FileInfoPanel.test.tsx +101 -0
- package/{notes-app → notes/app}/src/components/FileInfoPanel.tsx +135 -20
- package/notes/app/src/components/IssueCommentThread.test.tsx +93 -0
- package/notes/app/src/components/IssueCommentThread.tsx +156 -0
- package/notes/app/src/components/IssueDetailView.test.tsx +1118 -0
- package/notes/app/src/components/IssueDetailView.tsx +476 -0
- package/notes/app/src/components/IssueList.test.tsx +545 -0
- package/notes/app/src/components/IssueList.tsx +374 -0
- package/{notes-app → notes/app}/src/components/LabelSelector.test.tsx +94 -8
- package/notes/app/src/components/LabelSelector.tsx +452 -0
- package/{notes-app → notes/app}/src/components/MarkdownEditor.tsx +140 -2
- package/notes/app/src/components/PanelResizeHandle.test.tsx +481 -0
- package/notes/app/src/components/PanelResizeHandle.tsx +96 -0
- package/{notes-app → notes/app}/src/hooks/useEditSession.test.ts +171 -23
- package/{notes-app → notes/app}/src/hooks/useEditSession.ts +49 -62
- package/notes/app/src/hooks/useMdUpLayout.test.ts +10 -0
- package/notes/app/src/hooks/useMdUpLayout.ts +27 -0
- package/notes/app/src/hooks/usePeriodicSync.test.ts +244 -0
- package/notes/app/src/hooks/usePeriodicSync.ts +83 -0
- package/notes/app/src/hooks/useResizablePanelWidth.test.ts +251 -0
- package/notes/app/src/hooks/useResizablePanelWidth.ts +167 -0
- package/{notes-app → notes/app}/src/lib/api.test.ts +81 -1
- package/{notes-app → notes/app}/src/lib/api.ts +98 -4
- package/notes/app/src/lib/commentAnchor.test.ts +22 -0
- package/notes/app/src/lib/commentAnchor.ts +28 -0
- package/notes/app/src/lib/commentHighlight.test.ts +110 -0
- package/notes/app/src/lib/commentHighlight.ts +44 -0
- package/notes/app/src/lib/github-sync.test.ts +126 -0
- package/notes/app/src/lib/github-sync.ts +89 -0
- package/notes/app/src/lib/issue-list-utils.test.ts +275 -0
- package/notes/app/src/lib/issue-list-utils.ts +135 -0
- package/notes/app/src/lib/issue-mobile-layout.test.ts +17 -0
- package/notes/app/src/lib/issue-mobile-layout.ts +20 -0
- package/notes/app/src/lib/issue-url-sync.test.ts +73 -0
- package/notes/app/src/lib/issue-url-sync.ts +43 -0
- package/notes/app/src/lib/main-page-default-view.test.ts +37 -0
- package/notes/app/src/lib/main-page-default-view.ts +15 -0
- package/notes/app/src/lib/resizable-panel.test.ts +199 -0
- package/notes/app/src/lib/resizable-panel.ts +136 -0
- package/notes/app/src/pages/LoginPage.test.tsx +17 -0
- package/notes/app/src/pages/MainPage.branch-modal.test.tsx +226 -0
- package/notes/app/src/pages/MainPage.issue-sync.test.tsx +145 -0
- package/notes/app/src/pages/MainPage.sidebar.test.tsx +361 -0
- package/{notes-app → notes/app}/src/pages/MainPage.tsx +406 -61
- package/{notes-app → notes/app}/src/pages/SettingsPage.test.tsx +32 -23
- package/{notes-app → notes/app}/src/pages/UserManagementPage.test.tsx +8 -18
- package/notes/app/src/test/mocks.ts +156 -0
- package/notes/app/src/test/render.tsx +27 -0
- package/package.json +3 -5
- package/notes-api/tests/test_health.py +0 -11
- package/notes-app/docker/Dockerfile.e2e +0 -14
- package/notes-app/docker/Dockerfile.prod +0 -12
- package/notes-app/docker/Dockerfile.test +0 -12
- package/notes-app/e2e/file-tree.spec.ts +0 -29
- package/notes-app/e2e/file-view.spec.ts +0 -24
- package/notes-app/e2e/login.spec.ts +0 -32
- package/notes-app/src/components/IssueDetailView.test.tsx +0 -201
- package/notes-app/src/components/IssueDetailView.tsx +0 -318
- package/notes-app/src/components/IssueList.test.tsx +0 -93
- package/notes-app/src/components/IssueList.tsx +0 -169
- package/notes-app/src/components/LabelSelector.tsx +0 -276
- package/notes-app/src/pages/LoginPage.test.tsx +0 -29
- package/stacks/django-vue.yaml +0 -33
- package/stacks/fastapi-react.yaml +0 -160
- package/stacks/flutter-firebase.yaml +0 -58
- package/stacks/nextjs-prisma.yaml +0 -33
- package/stacks/schema.json +0 -144
- package/templates/agents/architect.hbs +0 -183
- package/templates/agents/auto-documenter.hbs +0 -36
- package/templates/agents/backend-developer.hbs +0 -175
- package/templates/agents/code-reviewer.hbs +0 -29
- package/templates/agents/database-developer.hbs +0 -52
- package/templates/agents/debug-specialist.hbs +0 -39
- package/templates/agents/environment-setup.hbs +0 -41
- package/templates/agents/frontend-developer.hbs +0 -45
- package/templates/agents/integration-qa.hbs +0 -26
- package/templates/agents/performance-qa.hbs +0 -24
- package/templates/agents/pr-writer.hbs +0 -53
- package/templates/agents/quality-lead.hbs +0 -26
- package/templates/agents/requirements-analyst.hbs +0 -26
- package/templates/agents/security-analyst.hbs +0 -21
- package/templates/agents/security-reviewer.hbs +0 -20
- package/templates/agents/ssot-updater.hbs +0 -45
- package/templates/agents/technical-writer.hbs +0 -28
- package/templates/agents/unit-test-engineer.hbs +0 -19
- package/templates/agents/user-clone.hbs +0 -43
- package/templates/github-actions/aws/deploy-notes.yml.hbs +0 -117
- package/templates/github-actions/azure/deploy-notes.yml.hbs +0 -78
- package/templates/github-actions/gcp/deploy-notes.yml.hbs +0 -76
- package/templates/github-actions/vercel-railway/deploy-notes.yml.hbs +0 -49
- package/templates/mindsets/agent-stdio.hbs +0 -51
- package/templates/mindsets/dart.hbs +0 -39
- package/templates/mindsets/ddd-clean-architecture.hbs +0 -52
- package/templates/mindsets/electron.hbs +0 -41
- package/templates/mindsets/fastapi.hbs +0 -40
- package/templates/mindsets/flutter.hbs +0 -56
- package/templates/mindsets/kotlin.hbs +0 -46
- package/templates/mindsets/postgresql.hbs +0 -39
- package/templates/mindsets/python.hbs +0 -39
- package/templates/mindsets/react.hbs +0 -51
- package/templates/mindsets/swift.hbs +0 -46
- package/templates/mindsets/terraform.hbs +0 -53
- package/templates/mindsets/typescript.hbs +0 -34
- package/templates/mindsets/vite.hbs +0 -37
- package/templates/notes-compose.yml +0 -82
- package/templates/partials/cogito-geo.hbs +0 -84
- package/templates/partials/cogito-godd.hbs +0 -22
- package/templates/partials/cogito-grammar.hbs +0 -105
- package/templates/partials/cogito-particle.hbs +0 -76
- package/templates/partials/cogito-root-ai.hbs +0 -79
- package/templates/partials/cogito-root-arch.hbs +0 -75
- package/templates/partials/cogito-root-core.hbs +0 -75
- package/templates/partials/cogito-root-daily.hbs +0 -58
- package/templates/partials/cogito-root-dev.hbs +0 -71
- package/templates/partials/cogito-root-devops.hbs +0 -71
- package/templates/partials/cogito-root-framework.hbs +0 -80
- package/templates/partials/cogito-root-lang.hbs +0 -74
- package/templates/partials/godd-core.hbs +0 -50
- package/templates/partials/project-context.hbs +0 -52
- package/templates/partials/ssot-header.hbs +0 -40
- package/templates/partials/ssot-rules.hbs +0 -30
- package/templates/prompts/adr.hbs +0 -39
- package/templates/prompts/agent-dev-workflow.hbs +0 -34
- package/templates/prompts/analyze-report.hbs +0 -36
- package/templates/prompts/auto-documentation.hbs +0 -37
- package/templates/prompts/check.hbs +0 -52
- package/templates/prompts/commit.hbs +0 -96
- package/templates/prompts/dev.hbs +0 -127
- package/templates/prompts/docs-init.hbs +0 -101
- package/templates/prompts/docs-update.hbs +0 -51
- package/templates/prompts/git-workflow.hbs +0 -125
- package/templates/prompts/impact-analysis.hbs +0 -43
- package/templates/prompts/install.hbs +0 -40
- package/templates/prompts/new-branch.hbs +0 -61
- package/templates/prompts/notes-deploy.hbs +0 -138
- package/templates/prompts/pr-analyze.hbs +0 -44
- package/templates/prompts/pr-create.hbs +0 -125
- package/templates/prompts/push-execute.hbs +0 -83
- package/templates/prompts/push.hbs +0 -24
- package/templates/prompts/release-notes.hbs +0 -38
- package/templates/prompts/requirements-to-business-flow.hbs +0 -29
- package/templates/prompts/requirements-to-tickets.hbs +0 -26
- package/templates/prompts/review.hbs +0 -126
- package/templates/prompts/setup.hbs +0 -247
- package/templates/prompts/spec.hbs +0 -35
- package/templates/prompts/specification-to-tickets.hbs +0 -24
- package/templates/prompts/submit.hbs +0 -143
- package/templates/prompts/sync.hbs +0 -63
- package/templates/prompts/test-plan.hbs +0 -56
- package/templates/scripts/aws/terraform-backend-setup.sh.hbs +0 -149
- package/templates/terraform/aws/alb.tf.hbs +0 -58
- package/templates/terraform/aws/backend.tf.hbs +0 -10
- package/templates/terraform/aws/cloudfront.tf.hbs +0 -96
- package/templates/terraform/aws/ecr.tf.hbs +0 -30
- package/templates/terraform/aws/ecs.tf.hbs +0 -142
- package/templates/terraform/aws/iam.tf.hbs +0 -88
- package/templates/terraform/aws/local.tf.hbs +0 -34
- package/templates/terraform/aws/output.tf.hbs +0 -49
- package/templates/terraform/aws/provider.tf.hbs +0 -26
- package/templates/terraform/aws/rds.tf.hbs +0 -38
- package/templates/terraform/aws/s3.tf.hbs +0 -34
- package/templates/terraform/aws/secrets.tf.hbs +0 -53
- package/templates/terraform/aws/security_groups.tf.hbs +0 -81
- package/templates/terraform/aws/vpc.tf.hbs +0 -63
- package/templates/terraform/azure/backend.tf.hbs +0 -8
- package/templates/terraform/azure/iam.tf.hbs +0 -36
- package/templates/terraform/azure/main.tf.hbs +0 -203
- package/templates/terraform/azure/output.tf.hbs +0 -19
- package/templates/terraform/azure/provider.tf.hbs +0 -19
- package/templates/terraform/gcp/backend.tf.hbs +0 -6
- package/templates/terraform/gcp/iam.tf.hbs +0 -47
- package/templates/terraform/gcp/main.tf.hbs +0 -175
- package/templates/terraform/gcp/output.tf.hbs +0 -15
- package/templates/terraform/gcp/provider.tf.hbs +0 -19
- package/templates/terraform/vercel-railway/main.tf.hbs +0 -64
- package/templates/terraform/vercel-railway/output.tf.hbs +0 -11
- /package/{notes-api → notes/api}/.dockerignore +0 -0
- /package/{notes-api → notes/api}/alembic/README +0 -0
- /package/{notes-api → notes/api}/alembic/env.py +0 -0
- /package/{notes-api → notes/api}/alembic/script.py.mako +0 -0
- /package/{notes-api → notes/api}/alembic/versions/001_initial_notes_tables.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/002_add_performance_indexes.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/003_add_yjs_documents.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/004_add_comments.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/005_add_github_username.py +0 -0
- /package/{notes-api → notes/api}/alembic/versions/006_add_check_constraints.py +0 -0
- /package/{notes-api → notes/api}/alembic.ini +0 -0
- /package/{notes-api → notes/api}/app/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/config.py +0 -0
- /package/{notes-api → notes/api}/app/database.py +0 -0
- /package/{notes-api → notes/api}/app/http_client.py +0 -0
- /package/{notes-api → notes/api}/app/models/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/models/audit.py +0 -0
- /package/{notes-api → notes/api}/app/models/base.py +0 -0
- /package/{notes-api → notes/api}/app/models/collab.py +0 -0
- /package/{notes-api → notes/api}/app/models/comment.py +0 -0
- /package/{notes-api → notes/api}/app/models/session.py +0 -0
- /package/{notes-api → notes/api}/app/models/setting.py +0 -0
- /package/{notes-api → notes/api}/app/models/user.py +0 -0
- /package/{notes-api → notes/api}/app/path_validation.py +0 -0
- /package/{notes-api → notes/api}/app/routers/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/routers/collab.py +0 -0
- /package/{notes-api → notes/api}/app/routers/comments.py +0 -0
- /package/{notes-api → notes/api}/app/routers/files.py +0 -0
- /package/{notes-api → notes/api}/app/routers/settings.py +0 -0
- /package/{notes-api → notes/api}/app/routers/tree.py +0 -0
- /package/{notes-api → notes/api}/app/routers/upload.py +0 -0
- /package/{notes-api → notes/api}/app/routers/users.py +0 -0
- /package/{notes-api → notes/api}/app/schemas/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/schemas/auth.py +0 -0
- /package/{notes-api → notes/api}/app/schemas/user.py +0 -0
- /package/{notes-api → notes/api}/app/security.py +0 -0
- /package/{notes-api → notes/api}/app/services/__init__.py +0 -0
- /package/{notes-api → notes/api}/app/services/audit.py +0 -0
- /package/{notes-api → notes/api}/app/services/business_date.py +0 -0
- /package/{notes-api → notes/api}/app/services/collab.py +0 -0
- /package/{notes-api → notes/api}/app/services/github.py +0 -0
- /package/{notes-api → notes/api}/app/services/pr_chain.py +0 -0
- /package/{notes-api → notes/api}/app/startup.py +0 -0
- /package/{notes-api → notes/api}/docker/Dockerfile +0 -0
- /package/{notes-api → notes/api}/docker/Dockerfile.test +0 -0
- /package/{notes-api → notes/api}/pyproject.toml +0 -0
- /package/{notes-api → notes/api}/tests/conftest.py +0 -0
- /package/{notes-api → notes/api}/tests/test_audit.py +0 -0
- /package/{notes-api → notes/api}/tests/test_auth.py +0 -0
- /package/{notes-api → notes/api}/tests/test_business_date.py +0 -0
- /package/{notes-api → notes/api}/tests/test_collab.py +0 -0
- /package/{notes-api → notes/api}/tests/test_comments.py +0 -0
- /package/{notes-api → notes/api}/tests/test_config_ssl.py +0 -0
- /package/{notes-api → notes/api}/tests/test_config_warnings.py +0 -0
- /package/{notes-api → notes/api}/tests/test_files.py +0 -0
- /package/{notes-api → notes/api}/tests/test_path_validation.py +0 -0
- /package/{notes-api → notes/api}/tests/test_security.py +0 -0
- /package/{notes-api → notes/api}/tests/test_settings.py +0 -0
- /package/{notes-api → notes/api}/tests/test_tree.py +0 -0
- /package/{notes-api → notes/api}/tests/test_upload.py +0 -0
- /package/{notes-api → notes/api}/tests/test_users.py +0 -0
- /package/{notes-app → notes/app}/.dockerignore +0 -0
- /package/{notes-app → notes/app}/index.html +0 -0
- /package/{notes-app → notes/app}/nginx.conf +0 -0
- /package/{notes-app → notes/app}/package-lock.json +0 -0
- /package/{notes-app → notes/app}/playwright.config.ts +0 -0
- /package/{notes-app → notes/app}/pnpm-lock.yaml +0 -0
- /package/{notes-app → notes/app}/src/App.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/BranchSelectorModal.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/CodeBlockView.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/CodeBlockView.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/CollabStatus.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/DiffPreview.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/DrawioEditor.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/DrawioViewer.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/ErrorBoundary.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/ErrorBoundary.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileContentView.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileContextMenu.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileContextMenu.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileDialogs.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/FileDialogs.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SaveStatusIndicator.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SaveStatusIndicator.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/Skeleton.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SpreadsheetEditor.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/SpreadsheetEditor.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/TextEditor.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/ToastContainer.tsx +0 -0
- /package/{notes-app → notes/app}/src/components/TreeNode.tsx +0 -0
- /package/{notes-app → notes/app}/src/contexts/AuthContext.test.tsx +0 -0
- /package/{notes-app → notes/app}/src/contexts/AuthContext.tsx +0 -0
- /package/{notes-app → notes/app}/src/contexts/ToastContext.test.ts +0 -0
- /package/{notes-app → notes/app}/src/contexts/ToastContext.tsx +0 -0
- /package/{notes-app → notes/app}/src/hooks/useAutoSave.test.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useAutoSave.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useCollaboration.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useComments.ts +0 -0
- /package/{notes-app → notes/app}/src/hooks/useYText.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/branch-diff.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/branch-diff.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/created-file.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/created-file.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/csv-utils.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/csv-utils.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/deepLinkPathToSearch.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/deepLinkPathToSearch.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/draft-storage.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/draft-storage.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/mermaid-render.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/postLoginRedirect.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/postLoginRedirect.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/tree-utils.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/user-colors.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/xlsx-utils.test.ts +0 -0
- /package/{notes-app → notes/app}/src/lib/xlsx-utils.ts +0 -0
- /package/{notes-app → notes/app}/src/main.tsx +0 -0
- /package/{notes-app → notes/app}/src/pages/LoginPage.tsx +0 -0
- /package/{notes-app → notes/app}/src/pages/MainPage.insertChildEntry.test.ts +0 -0
- /package/{notes-app → notes/app}/src/pages/SettingsPage.tsx +0 -0
- /package/{notes-app → notes/app}/src/pages/UserManagementPage.tsx +0 -0
- /package/{notes-app → notes/app}/src/styles/globals.css +0 -0
- /package/{notes-app → notes/app}/src/types.ts +0 -0
- /package/{notes-app → notes/app}/src/vite-env.d.ts +0 -0
- /package/{notes-app → notes/app}/tsconfig.json +0 -0
- /package/{notes-app → notes/app}/tsconfig.node.json +0 -0
- /package/{notes-app → notes/app}/vite.config.js +0 -0
- /package/{notes-app → notes/app}/vite.config.ts +0 -0
- /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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
151
|
-
|
|
219
|
+
if (isStale()) return;
|
|
220
|
+
if (!background) {
|
|
221
|
+
setBranches([]);
|
|
222
|
+
showToast(getErrorMessage(err, "ブランチ一覧の取得に失敗しました"), "error");
|
|
223
|
+
}
|
|
152
224
|
})
|
|
153
|
-
.finally(() =>
|
|
154
|
-
|
|
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
|
|
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(
|
|
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 (
|
|
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
|
-
|
|
448
|
+
initialDeepLinkLoaded.current = true;
|
|
354
449
|
handleFileSelect(fileParam);
|
|
450
|
+
return;
|
|
355
451
|
}
|
|
356
|
-
|
|
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(
|
|
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
|
|
624
|
-
|
|
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,
|
|
952
|
+
{/* Sidebar — fixed drawer on mobile, relative on md+ */}
|
|
649
953
|
<aside
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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={() =>
|
|
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
|
-
|
|
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={
|
|
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={
|
|
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={
|
|
1021
|
-
|
|
1022
|
-
|
|
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
|
|
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
|
-
|
|
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
|
}
|