@ripla/godd-mcp 1.0.4-canary.9 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 -49
- 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
|
@@ -43,6 +43,15 @@ function mockResponse(body: unknown, status = 200): Response {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
const apiFetchMock = vi.fn<(path: string, init?: RequestInit) => Promise<Response>>();
|
|
46
|
+
const triggerPeriodicSync = vi.hoisted(() => vi.fn());
|
|
47
|
+
|
|
48
|
+
vi.mock("@/hooks/usePeriodicSync", () => ({
|
|
49
|
+
usePeriodicSync: ({ onSync, enabled }: { onSync: () => void | Promise<void>; enabled: boolean }) => {
|
|
50
|
+
triggerPeriodicSync.mockImplementation(() => {
|
|
51
|
+
if (enabled) void onSync();
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
}));
|
|
46
55
|
|
|
47
56
|
vi.mock("@/lib/api", async (importOriginal) => {
|
|
48
57
|
const actual = await importOriginal<typeof import("@/lib/api")>();
|
|
@@ -138,6 +147,31 @@ describe("useEditSession", () => {
|
|
|
138
147
|
expect(result.current.error).toBeNull();
|
|
139
148
|
});
|
|
140
149
|
|
|
150
|
+
it("保存後の fetchSession で prNumber / prUrl が更新される", async () => {
|
|
151
|
+
const saveResult = { branch: "edit/user", sha: "abc123", commitCount: 4 };
|
|
152
|
+
const updatedSession = {
|
|
153
|
+
...MOCK_SESSION,
|
|
154
|
+
prNumber: 99,
|
|
155
|
+
prUrl: "https://github.com/org/repo/pull/99",
|
|
156
|
+
};
|
|
157
|
+
apiFetchMock
|
|
158
|
+
.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION }))
|
|
159
|
+
.mockResolvedValueOnce(mockResponse(saveResult))
|
|
160
|
+
.mockResolvedValueOnce(mockResponse({ session: updatedSession }));
|
|
161
|
+
|
|
162
|
+
const { result } = renderHook(() => useEditSession());
|
|
163
|
+
await waitFor(() => expect(result.current.session?.prNumber).toBe(42));
|
|
164
|
+
|
|
165
|
+
await act(async () => {
|
|
166
|
+
await result.current.saveFile("docs/README.md", "new content", "sha123");
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
await waitFor(() => {
|
|
170
|
+
expect(result.current.session?.prNumber).toBe(99);
|
|
171
|
+
expect(result.current.session?.prUrl).toBe(updatedSession.prUrl);
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
|
|
141
175
|
it("sha が null の場合 body に sha を含めない", async () => {
|
|
142
176
|
const saveResult = { branch: "edit/user", sha: "abc123", commitCount: 1 };
|
|
143
177
|
apiFetchMock
|
|
@@ -231,40 +265,154 @@ describe("useEditSession", () => {
|
|
|
231
265
|
});
|
|
232
266
|
});
|
|
233
267
|
|
|
234
|
-
describe("
|
|
235
|
-
it("
|
|
268
|
+
describe("periodic sync", () => {
|
|
269
|
+
it("背景 poll で prGithub.state が更新される", async () => {
|
|
270
|
+
apiFetchMock.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION }));
|
|
271
|
+
|
|
272
|
+
const { result } = renderHook(() => useEditSession({ syncEnabled: true }));
|
|
273
|
+
await waitFor(() => expect(result.current.session?.prGithub?.state).toBe("open"));
|
|
274
|
+
|
|
275
|
+
const mergedSession: EditSession = {
|
|
276
|
+
...MOCK_SESSION,
|
|
277
|
+
prGithub: {
|
|
278
|
+
...MOCK_SESSION.prGithub!,
|
|
279
|
+
state: "closed",
|
|
280
|
+
merged: true,
|
|
281
|
+
},
|
|
282
|
+
};
|
|
283
|
+
apiFetchMock.mockResolvedValueOnce(mockResponse({ session: mergedSession }));
|
|
284
|
+
|
|
285
|
+
await act(async () => {
|
|
286
|
+
triggerPeriodicSync();
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
await waitFor(() => {
|
|
290
|
+
expect(result.current.session?.prGithub?.state).toBe("closed");
|
|
291
|
+
expect(result.current.session?.prGithub?.merged).toBe(true);
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it("背景 poll で 404 のとき session を維持する", async () => {
|
|
296
|
+
apiFetchMock.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION }));
|
|
297
|
+
|
|
298
|
+
const { result } = renderHook(() => useEditSession({ syncEnabled: true }));
|
|
299
|
+
await waitFor(() => expect(result.current.session).toEqual(MOCK_SESSION));
|
|
300
|
+
|
|
301
|
+
apiFetchMock.mockResolvedValueOnce(mockResponse(null, 404));
|
|
302
|
+
|
|
303
|
+
await act(async () => {
|
|
304
|
+
triggerPeriodicSync();
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
expect(result.current.session).toEqual(MOCK_SESSION);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it("背景 poll 失敗時は session を維持する", async () => {
|
|
311
|
+
apiFetchMock.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION }));
|
|
312
|
+
|
|
313
|
+
const { result } = renderHook(() => useEditSession({ syncEnabled: true }));
|
|
314
|
+
await waitFor(() => expect(result.current.session).toEqual(MOCK_SESSION));
|
|
315
|
+
|
|
316
|
+
apiFetchMock.mockRejectedValueOnce(new Error("Network error"));
|
|
317
|
+
|
|
318
|
+
await act(async () => {
|
|
319
|
+
triggerPeriodicSync();
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
expect(result.current.session).toEqual(MOCK_SESSION);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("mutation 直後の foreground 再取得中に到着した古い background poll 応答が新しい session を上書きしない(#935)", async () => {
|
|
326
|
+
const FRESH: EditSession = { ...MOCK_SESSION, commitCount: 4, prNumber: 99 };
|
|
327
|
+
const saveResult = { branch: "edit/user", sha: "abc123", commitCount: 4 };
|
|
328
|
+
|
|
329
|
+
let resolveStalePoll: ((r: Response) => void) | undefined;
|
|
330
|
+
const stalePoll = new Promise<Response>((resolve) => {
|
|
331
|
+
resolveStalePoll = resolve;
|
|
332
|
+
});
|
|
333
|
+
|
|
236
334
|
apiFetchMock
|
|
237
|
-
.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION }))
|
|
238
|
-
.
|
|
239
|
-
.mockResolvedValueOnce(mockResponse(
|
|
335
|
+
.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION })) // 1: mount foreground
|
|
336
|
+
.mockReturnValueOnce(stalePoll) // 2: background poll(in-flight・古い応答は後で返す)
|
|
337
|
+
.mockResolvedValueOnce(mockResponse(saveResult)) // 3: PUT save
|
|
338
|
+
.mockResolvedValueOnce(mockResponse({ session: FRESH })); // 4: 保存後の foreground 再取得
|
|
240
339
|
|
|
241
|
-
const { result } = renderHook(() => useEditSession());
|
|
242
|
-
await waitFor(() => expect(result.current.session).
|
|
340
|
+
const { result } = renderHook(() => useEditSession({ syncEnabled: true }));
|
|
341
|
+
await waitFor(() => expect(result.current.session?.commitCount).toBe(3));
|
|
243
342
|
|
|
244
|
-
|
|
343
|
+
// background poll を in-flight にする(beginPollRequest で poll seq が進む)
|
|
245
344
|
await act(async () => {
|
|
246
|
-
|
|
345
|
+
triggerPeriodicSync();
|
|
346
|
+
await Promise.resolve();
|
|
247
347
|
});
|
|
248
348
|
|
|
249
|
-
|
|
250
|
-
|
|
349
|
+
// mutation(保存)→ foreground 再取得が新しい session を適用(beginLoadRequest が in-flight poll を無効化)
|
|
350
|
+
await act(async () => {
|
|
351
|
+
await result.current.saveFile("docs/README.md", "new", "sha123");
|
|
352
|
+
});
|
|
353
|
+
await waitFor(() => expect(result.current.session?.commitCount).toBe(4));
|
|
354
|
+
expect(result.current.session?.prNumber).toBe(99);
|
|
355
|
+
|
|
356
|
+
// 後着の古い background poll 応答が到着しても、新しい session を上書きしない
|
|
357
|
+
await act(async () => {
|
|
358
|
+
resolveStalePoll?.(mockResponse({ session: MOCK_SESSION }));
|
|
359
|
+
await Promise.resolve();
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
expect(result.current.session?.commitCount).toBe(4);
|
|
363
|
+
expect(result.current.session?.prNumber).toBe(99);
|
|
251
364
|
});
|
|
252
365
|
|
|
253
|
-
it("
|
|
366
|
+
it("mutation 開始時に in-flight の background poll を無効化し、PUT 実行中に到着した古い応答を適用しない(#935)", async () => {
|
|
367
|
+
const STALE = { ...MOCK_SESSION, prNumber: 7 };
|
|
368
|
+
const FRESH = { ...MOCK_SESSION, prNumber: 99, commitCount: 4 };
|
|
369
|
+
const saveResult = { branch: "edit/user", sha: "abc123", commitCount: 4 };
|
|
370
|
+
|
|
371
|
+
let resolveStalePoll: ((r: Response) => void) | undefined;
|
|
372
|
+
const stalePoll = new Promise<Response>((resolve) => {
|
|
373
|
+
resolveStalePoll = resolve;
|
|
374
|
+
});
|
|
375
|
+
let resolvePut: ((r: Response) => void) | undefined;
|
|
376
|
+
const putPromise = new Promise<Response>((resolve) => {
|
|
377
|
+
resolvePut = resolve;
|
|
378
|
+
});
|
|
379
|
+
|
|
254
380
|
apiFetchMock
|
|
255
|
-
.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION }))
|
|
256
|
-
.
|
|
381
|
+
.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION })) // 1: mount foreground
|
|
382
|
+
.mockReturnValueOnce(stalePoll) // 2: background poll(in-flight)
|
|
383
|
+
.mockReturnValueOnce(putPromise) // 3: PUT save(in-flight に保持)
|
|
384
|
+
.mockResolvedValueOnce(mockResponse({ session: FRESH })); // 4: 保存後の foreground 再取得
|
|
257
385
|
|
|
258
|
-
const { result } = renderHook(() => useEditSession());
|
|
259
|
-
await waitFor(() => expect(result.current.session).
|
|
386
|
+
const { result } = renderHook(() => useEditSession({ syncEnabled: true }));
|
|
387
|
+
await waitFor(() => expect(result.current.session?.prNumber).toBe(42));
|
|
260
388
|
|
|
261
|
-
|
|
389
|
+
// background poll を in-flight にする
|
|
262
390
|
await act(async () => {
|
|
263
|
-
|
|
391
|
+
triggerPeriodicSync();
|
|
392
|
+
await Promise.resolve();
|
|
264
393
|
});
|
|
265
394
|
|
|
266
|
-
|
|
267
|
-
|
|
395
|
+
// saveFile を開始(PUT は in-flight のまま保持)。開始時の beginPollRequest が in-flight poll を無効化する
|
|
396
|
+
let savePromise: Promise<unknown> | undefined;
|
|
397
|
+
await act(async () => {
|
|
398
|
+
savePromise = result.current.saveFile("docs/README.md", "new", "sha123");
|
|
399
|
+
await Promise.resolve();
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// PUT 実行中に古い background poll が解決しても、無効化済みのため適用されない
|
|
403
|
+
await act(async () => {
|
|
404
|
+
resolveStalePoll?.(mockResponse({ session: STALE }));
|
|
405
|
+
await Promise.resolve();
|
|
406
|
+
});
|
|
407
|
+
expect(result.current.session?.prNumber).toBe(42); // STALE(7) で上書きされない
|
|
408
|
+
|
|
409
|
+
// PUT 完了 → 保存後 foreground 取得が新しい session を適用
|
|
410
|
+
await act(async () => {
|
|
411
|
+
resolvePut?.(mockResponse(saveResult));
|
|
412
|
+
await savePromise;
|
|
413
|
+
});
|
|
414
|
+
expect(result.current.session?.prNumber).toBe(99);
|
|
415
|
+
expect(result.current.session?.commitCount).toBe(4);
|
|
268
416
|
});
|
|
269
417
|
});
|
|
270
418
|
|
|
@@ -272,15 +420,15 @@ describe("useEditSession", () => {
|
|
|
272
420
|
it("error を null にリセットする", async () => {
|
|
273
421
|
apiFetchMock
|
|
274
422
|
.mockResolvedValueOnce(mockResponse({ session: MOCK_SESSION }))
|
|
275
|
-
.mockResolvedValueOnce(mockResponse({ detail: "
|
|
423
|
+
.mockResolvedValueOnce(mockResponse({ detail: "No changes" }, 400));
|
|
276
424
|
|
|
277
425
|
const { result } = renderHook(() => useEditSession());
|
|
278
426
|
await waitFor(() => expect(result.current.session).toBeTruthy());
|
|
279
427
|
|
|
280
428
|
await act(async () => {
|
|
281
|
-
await result.current.
|
|
429
|
+
await result.current.createPr();
|
|
282
430
|
});
|
|
283
|
-
expect(result.current.error).
|
|
431
|
+
expect(result.current.error).toBe("No changes");
|
|
284
432
|
|
|
285
433
|
act(() => {
|
|
286
434
|
result.current.clearError();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { useState, useCallback, useEffect, useRef } from "react";
|
|
2
2
|
import { apiFetch, encodePath, getErrorMessage } from "@/lib/api";
|
|
3
|
+
import { usePeriodicSync } from "@/hooks/usePeriodicSync";
|
|
4
|
+
import { GITHUB_SYNC_POLL_INTERVAL_MS } from "@/lib/github-sync";
|
|
3
5
|
|
|
4
6
|
/** `/pr/my-session` が GitHub から付与する PR のライブ状態(DB の prNumber/prUrl は保持) */
|
|
5
7
|
export type SessionPrGithub = {
|
|
@@ -33,43 +35,75 @@ type SaveResult = {
|
|
|
33
35
|
prWarning?: string;
|
|
34
36
|
};
|
|
35
37
|
|
|
36
|
-
export function useEditSession() {
|
|
38
|
+
export function useEditSession(options?: { syncEnabled?: boolean }) {
|
|
39
|
+
const syncEnabled = options?.syncEnabled ?? false;
|
|
37
40
|
const [session, setSession] = useState<EditSession | null>(null);
|
|
38
41
|
const [saving, setSaving] = useState(false);
|
|
39
42
|
const [error, setError] = useState<string | null>(null);
|
|
40
43
|
const mountedRef = useRef(true);
|
|
44
|
+
// foreground(初回・mutation 後の再取得)と background poll で seq を分離し、並走時に
|
|
45
|
+
// 先着の古い応答が後着の新しい session を上書きしないようにする(#935、#934 と同型)
|
|
46
|
+
const loadRequestSeq = useRef(0);
|
|
47
|
+
const pollRequestSeq = useRef(0);
|
|
41
48
|
|
|
42
49
|
useEffect(() => {
|
|
43
50
|
mountedRef.current = true;
|
|
44
51
|
return () => { mountedRef.current = false; };
|
|
45
52
|
}, []);
|
|
46
53
|
|
|
47
|
-
|
|
54
|
+
// foreground 取得開始時は poll seq も進め、in-flight の background poll を無効化する
|
|
55
|
+
const beginLoadRequest = useCallback(() => {
|
|
56
|
+
loadRequestSeq.current += 1;
|
|
57
|
+
pollRequestSeq.current += 1;
|
|
58
|
+
return loadRequestSeq.current;
|
|
59
|
+
}, []);
|
|
60
|
+
|
|
61
|
+
// background poll は poll seq のみ進め、in-flight の foreground 取得は無効化しない
|
|
62
|
+
const beginPollRequest = useCallback(() => {
|
|
63
|
+
pollRequestSeq.current += 1;
|
|
64
|
+
return pollRequestSeq.current;
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
const fetchSession = useCallback(async (options?: { background?: boolean }) => {
|
|
68
|
+
const background = options?.background ?? false;
|
|
69
|
+
const requestId = background ? beginPollRequest() : beginLoadRequest();
|
|
70
|
+
const isStale = () =>
|
|
71
|
+
background
|
|
72
|
+
? requestId !== pollRequestSeq.current
|
|
73
|
+
: requestId !== loadRequestSeq.current;
|
|
48
74
|
try {
|
|
49
75
|
const res = await apiFetch("/pr/my-session");
|
|
50
|
-
if (!mountedRef.current) return;
|
|
76
|
+
if (!mountedRef.current || isStale()) return;
|
|
51
77
|
if (res.ok) {
|
|
52
78
|
const data = await res.json();
|
|
53
|
-
if (!mountedRef.current) return;
|
|
79
|
+
if (!mountedRef.current || isStale()) return;
|
|
54
80
|
const s = data.session;
|
|
55
81
|
setSession(
|
|
56
82
|
s
|
|
57
83
|
? { ...s, prGithub: s.prGithub ?? null }
|
|
58
84
|
: null,
|
|
59
85
|
);
|
|
60
|
-
} else {
|
|
61
|
-
|
|
86
|
+
} else if (!background && mountedRef.current) {
|
|
87
|
+
setSession(null);
|
|
62
88
|
}
|
|
63
89
|
} catch {
|
|
64
|
-
if (!mountedRef.current) return;
|
|
65
|
-
setSession(null);
|
|
90
|
+
if (!mountedRef.current || isStale()) return;
|
|
91
|
+
if (!background) setSession(null);
|
|
66
92
|
}
|
|
67
|
-
}, []);
|
|
93
|
+
}, [beginLoadRequest, beginPollRequest]);
|
|
68
94
|
|
|
69
95
|
useEffect(() => {
|
|
70
96
|
void fetchSession();
|
|
71
97
|
}, [fetchSession]);
|
|
72
98
|
|
|
99
|
+
usePeriodicSync({
|
|
100
|
+
enabled: syncEnabled,
|
|
101
|
+
intervalMs: GITHUB_SYNC_POLL_INTERVAL_MS,
|
|
102
|
+
onSync: () => fetchSession({ background: true }),
|
|
103
|
+
// マウント時の初回 fetchSession と即時 sync の二重 dispatch を回避(#929)
|
|
104
|
+
skipInitialSync: true,
|
|
105
|
+
});
|
|
106
|
+
|
|
73
107
|
const saveFile = useCallback(
|
|
74
108
|
async (
|
|
75
109
|
path: string,
|
|
@@ -81,6 +115,8 @@ export function useEditSession() {
|
|
|
81
115
|
): Promise<SaveResult | null> => {
|
|
82
116
|
setSaving(true);
|
|
83
117
|
setError(null);
|
|
118
|
+
// mutation 中に in-flight の background poll が古い session を適用しないよう先行無効化(#935、IssueDetailView と同様)
|
|
119
|
+
beginPollRequest();
|
|
84
120
|
try {
|
|
85
121
|
const body: Record<string, unknown> = { content };
|
|
86
122
|
if (sha) body.sha = sha;
|
|
@@ -109,11 +145,13 @@ export function useEditSession() {
|
|
|
109
145
|
if (mountedRef.current) setSaving(false);
|
|
110
146
|
}
|
|
111
147
|
},
|
|
112
|
-
[fetchSession],
|
|
148
|
+
[fetchSession, beginPollRequest],
|
|
113
149
|
);
|
|
114
150
|
|
|
115
151
|
const createPr = useCallback(async (): Promise<string | null> => {
|
|
116
152
|
setError(null);
|
|
153
|
+
// mutation 中に in-flight の background poll が古い session を適用しないよう先行無効化(#935、IssueDetailView と同様)
|
|
154
|
+
beginPollRequest();
|
|
117
155
|
try {
|
|
118
156
|
const res = await apiFetch("/pr/create", { method: "POST" });
|
|
119
157
|
if (!res.ok) {
|
|
@@ -127,55 +165,7 @@ export function useEditSession() {
|
|
|
127
165
|
if (mountedRef.current) setError(getErrorMessage(err, "PR の作成に失敗しました"));
|
|
128
166
|
return null;
|
|
129
167
|
}
|
|
130
|
-
}, [fetchSession]);
|
|
131
|
-
|
|
132
|
-
const submitPr = useCallback(async (): Promise<boolean> => {
|
|
133
|
-
setError(null);
|
|
134
|
-
try {
|
|
135
|
-
const res = await apiFetch("/pr/submit", { method: "POST" });
|
|
136
|
-
if (!res.ok) {
|
|
137
|
-
const detail = await res.json().catch(() => null);
|
|
138
|
-
throw new Error(detail?.detail ?? `Submit failed: ${res.status}`);
|
|
139
|
-
}
|
|
140
|
-
await fetchSession();
|
|
141
|
-
return true;
|
|
142
|
-
} catch (err) {
|
|
143
|
-
if (mountedRef.current) setError(getErrorMessage(err, "PR の提出に失敗しました"));
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
}, [fetchSession]);
|
|
147
|
-
|
|
148
|
-
const convertToDraft = useCallback(async (): Promise<boolean> => {
|
|
149
|
-
setError(null);
|
|
150
|
-
try {
|
|
151
|
-
const res = await apiFetch("/pr/draft", { method: "POST" });
|
|
152
|
-
if (!res.ok) {
|
|
153
|
-
const detail = await res.json().catch(() => null);
|
|
154
|
-
throw new Error(detail?.detail ?? `Draft conversion failed: ${res.status}`);
|
|
155
|
-
}
|
|
156
|
-
await fetchSession();
|
|
157
|
-
return true;
|
|
158
|
-
} catch (err) {
|
|
159
|
-
if (mountedRef.current) setError(getErrorMessage(err, "ドラフトへの変更に失敗しました"));
|
|
160
|
-
return false;
|
|
161
|
-
}
|
|
162
|
-
}, [fetchSession]);
|
|
163
|
-
|
|
164
|
-
const discardChanges = useCallback(async (): Promise<boolean> => {
|
|
165
|
-
setError(null);
|
|
166
|
-
try {
|
|
167
|
-
const res = await apiFetch("/pr/discard", { method: "POST" });
|
|
168
|
-
if (!res.ok) {
|
|
169
|
-
const detail = await res.json().catch(() => null);
|
|
170
|
-
throw new Error(detail?.detail ?? `Discard failed: ${res.status}`);
|
|
171
|
-
}
|
|
172
|
-
await fetchSession();
|
|
173
|
-
return true;
|
|
174
|
-
} catch (err) {
|
|
175
|
-
if (mountedRef.current) setError(getErrorMessage(err, "変更の破棄に失敗しました"));
|
|
176
|
-
return false;
|
|
177
|
-
}
|
|
178
|
-
}, [fetchSession]);
|
|
168
|
+
}, [fetchSession, beginPollRequest]);
|
|
179
169
|
|
|
180
170
|
return {
|
|
181
171
|
session,
|
|
@@ -183,9 +173,6 @@ export function useEditSession() {
|
|
|
183
173
|
error,
|
|
184
174
|
saveFile,
|
|
185
175
|
createPr,
|
|
186
|
-
submitPr,
|
|
187
|
-
convertToDraft,
|
|
188
|
-
discardChanges,
|
|
189
176
|
clearError: () => setError(null),
|
|
190
177
|
};
|
|
191
178
|
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { resolvePanelWidthStyle } from "@/hooks/useMdUpLayout";
|
|
3
|
+
|
|
4
|
+
describe("resolvePanelWidthStyle", () => {
|
|
5
|
+
it("デスクトップレイアウト時のみ幅スタイルを返す", () => {
|
|
6
|
+
expect(resolvePanelWidthStyle(320, true)).toEqual({ width: 320 });
|
|
7
|
+
expect(resolvePanelWidthStyle(320, false)).toBeUndefined();
|
|
8
|
+
expect(resolvePanelWidthStyle(undefined, true)).toBeUndefined();
|
|
9
|
+
});
|
|
10
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
|
|
3
|
+
const MD_UP_QUERY = "(min-width: 768px)";
|
|
4
|
+
|
|
5
|
+
export function useMdUpLayout(): boolean {
|
|
6
|
+
const [mdUp, setMdUp] = useState(() =>
|
|
7
|
+
typeof window !== "undefined" ? window.matchMedia(MD_UP_QUERY).matches : false,
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
const media = window.matchMedia(MD_UP_QUERY);
|
|
12
|
+
const sync = () => setMdUp(media.matches);
|
|
13
|
+
sync();
|
|
14
|
+
media.addEventListener("change", sync);
|
|
15
|
+
return () => media.removeEventListener("change", sync);
|
|
16
|
+
}, []);
|
|
17
|
+
|
|
18
|
+
return mdUp;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function resolvePanelWidthStyle(
|
|
22
|
+
width: number | undefined,
|
|
23
|
+
applyCustomWidth: boolean,
|
|
24
|
+
): { width: number } | undefined {
|
|
25
|
+
if (!applyCustomWidth || width == null) return undefined;
|
|
26
|
+
return { width };
|
|
27
|
+
}
|