ui-syncup 0.3.13 → 0.4.0-beta.2
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/.agents/skills/ai-spec-workflow/SKILL.md +58 -0
- package/.agents/skills/ai-spec-workflow/references/AI_SPECIFICATION_WORKFLOW.md +1434 -0
- package/.agents/skills/ai-spec-workflow/references/templates/design-template.md +729 -0
- package/.agents/skills/ai-spec-workflow/references/templates/requirements-template.md +179 -0
- package/.agents/skills/ai-spec-workflow/references/templates/tasks-template.md +501 -0
- package/.agents/skills/animation-designer/SKILL.md +688 -0
- package/.agents/skills/animation-designer/manifest.yaml +44 -0
- package/.agents/skills/brainstorming/SKILL.md +54 -0
- package/.agents/skills/contract-driven-ui/SKILL.md +270 -0
- package/.agents/skills/dispatching-parallel-agents/SKILL.md +180 -0
- package/.agents/skills/executing-plans/SKILL.md +76 -0
- package/.agents/skills/executing-specs/SKILL.md +53 -0
- package/.agents/skills/finishing-a-development-branch/SKILL.md +200 -0
- package/.agents/skills/github-workflow-automation/SKILL.md +846 -0
- package/.agents/skills/react-best-practices/AGENTS.md +2249 -0
- package/.agents/skills/react-best-practices/README.md +123 -0
- package/.agents/skills/react-best-practices/SKILL.md +121 -0
- package/.agents/skills/react-best-practices/metadata.json +15 -0
- package/.agents/skills/react-best-practices/rules/_sections.md +46 -0
- package/.agents/skills/react-best-practices/rules/_template.md +28 -0
- package/.agents/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/.agents/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/.agents/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/.agents/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/.agents/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/.agents/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/.agents/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/.agents/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/.agents/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/.agents/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/.agents/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/.agents/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/.agents/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/.agents/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/.agents/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/.agents/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/.agents/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/.agents/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/.agents/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/.agents/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/.agents/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/.agents/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/.agents/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/.agents/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/.agents/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/.agents/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/.agents/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/.agents/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/.agents/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/.agents/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/.agents/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/.agents/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/.agents/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/.agents/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/.agents/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/.agents/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/.agents/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/.agents/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/.agents/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/.agents/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/.agents/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/.agents/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/.agents/skills/react-best-practices/rules/server-cache-react.md +26 -0
- package/.agents/skills/react-best-practices/rules/server-parallel-fetching.md +79 -0
- package/.agents/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/.agents/skills/react-ui-patterns/SKILL.md +289 -0
- package/.agents/skills/receiving-code-review/SKILL.md +213 -0
- package/.agents/skills/requesting-code-review/SKILL.md +105 -0
- package/.agents/skills/requesting-code-review/code-reviewer.md +146 -0
- package/.agents/skills/reviewing-code/SKILL.md +28 -0
- package/.agents/skills/shadcn/SKILL.md +240 -0
- package/.agents/skills/shadcn/agents/openai.yml +5 -0
- package/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/.agents/skills/shadcn/assets/shadcn.png +0 -0
- package/.agents/skills/shadcn/cli.md +255 -0
- package/.agents/skills/shadcn/customization.md +202 -0
- package/.agents/skills/shadcn/evals/evals.json +47 -0
- package/.agents/skills/shadcn/mcp.md +94 -0
- package/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/.agents/skills/shadcn/rules/composition.md +195 -0
- package/.agents/skills/shadcn/rules/forms.md +192 -0
- package/.agents/skills/shadcn/rules/icons.md +101 -0
- package/.agents/skills/shadcn/rules/styling.md +162 -0
- package/.agents/skills/steering-creation/SKILL.md +221 -0
- package/.agents/skills/steering-creation/references/STEERING_CREATION_INSTRUCTION.md +850 -0
- package/.agents/skills/subagent-driven-development/SKILL.md +240 -0
- package/.agents/skills/subagent-driven-development/code-quality-reviewer-prompt.md +20 -0
- package/.agents/skills/subagent-driven-development/implementer-prompt.md +78 -0
- package/.agents/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/.agents/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/.agents/skills/systematic-debugging/SKILL.md +296 -0
- package/.agents/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/.agents/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/.agents/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/.agents/skills/systematic-debugging/find-polluter.sh +63 -0
- package/.agents/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/.agents/skills/systematic-debugging/test-academic.md +14 -0
- package/.agents/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/.agents/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/.agents/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/.agents/skills/test-driven-development/SKILL.md +371 -0
- package/.agents/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/.agents/skills/using-git-worktrees/SKILL.md +217 -0
- package/.agents/skills/using-superpowers/SKILL.md +87 -0
- package/.agents/skills/verification-before-completion/SKILL.md +139 -0
- package/.agents/skills/web-design-guidelines/SKILL.md +36 -0
- package/.agents/skills/writing-plans/SKILL.md +116 -0
- package/.agents/skills/writing-skills/SKILL.md +655 -0
- package/.agents/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/.agents/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/.agents/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/.agents/skills/writing-skills/persuasion-principles.md +187 -0
- package/.agents/skills/writing-skills/render-graphs.js +168 -0
- package/.agents/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/.ai/steering/product.md +51 -0
- package/.ai/steering/structure.md +275 -0
- package/.ai/steering/tech.md +188 -0
- package/.claude/agents/database-architect.md +96 -0
- package/.claude/agents/deployment-pipeline-architect.md +122 -0
- package/.claude/agents/nextjs-expert.md +69 -0
- package/.claude/agents/ui-design-expert.md +106 -0
- package/.claudeignore +69 -0
- package/.dockerignore +8 -0
- package/.env.development +86 -0
- package/.env.example +171 -0
- package/.env.production +139 -0
- package/.env.test +58 -0
- package/.gitattributes +2 -0
- package/.github/ISSUE_TEMPLATE/bug_report.yml +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.yml +20 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +23 -0
- package/.github/workflows/ci.yml +64 -0
- package/.github/workflows/release.yml +174 -0
- package/.nvmrc +1 -0
- package/.releaserc.json +18 -0
- package/.vercelignore +73 -0
- package/AGENTS.md +544 -0
- package/CHANGELOG.md +69 -0
- package/CODE_OF_CONDUCT.md +21 -0
- package/CONTRIBUTING.md +32 -0
- package/Dockerfile +84 -0
- package/LICENSE +21 -0
- package/README.md +328 -59
- package/SECURITY.md +16 -0
- package/bun.lock +3853 -0
- package/cli/README.md +94 -0
- package/cli/bun.lock +306 -0
- package/cli/index.ts +96 -0
- package/cli/package-lock.json +2157 -0
- package/cli/package.json +30 -0
- package/cli/src/commands/backup.ts +78 -0
- package/cli/src/commands/doctor.ts +82 -0
- package/cli/src/commands/init.ts +234 -0
- package/cli/src/commands/logs.ts +26 -0
- package/cli/src/commands/open.ts +23 -0
- package/cli/src/commands/remove.ts +44 -0
- package/cli/src/commands/restart.ts +21 -0
- package/cli/src/commands/restore.ts +90 -0
- package/cli/src/commands/start.ts +26 -0
- package/cli/src/commands/status.ts +25 -0
- package/cli/src/commands/stop.ts +20 -0
- package/cli/src/commands/upgrade.ts +28 -0
- package/cli/src/lib/docker.ts +40 -0
- package/cli/src/lib/env.ts +42 -0
- package/cli/src/lib/ui.ts +43 -0
- package/cli/tsconfig.json +13 -0
- package/cli/tsup.config.ts +12 -0
- package/components.json +24 -0
- package/docker/README.md +430 -0
- package/docker/compose.dev-minio.yml +30 -0
- package/docker/compose.dev.yml +39 -0
- package/docker/compose.local.yml +84 -0
- package/docker/compose.yml +153 -0
- package/docs/VERSIONING.md +101 -0
- package/docs/database/DRIZZLE_COMMANDS_EXPLAINED.md +1779 -0
- package/docs/database/DRIZZLE_ZOD_POSTGRESQL_INSTRUCTION.md +646 -0
- package/docs/database/MIGRATION_BEST_PRACTICES.md +601 -0
- package/docs/database/MIGRATION_ROLLBACK.md +1080 -0
- package/docs/database/MIGRATION_SYSTEM.md +165 -0
- package/docs/database/MIGRATION_TROUBLESHOOTING.md +881 -0
- package/docs/development/ENVIRONMENT_CONFIG.md +896 -0
- package/docs/development/LOCAL_DEVELOPMENT.md +456 -0
- package/docs/development/REMOTE_DATABASE_SETUP.md +786 -0
- package/docs/development/STORAGE_SETUP.md +207 -0
- package/docs/development/SUPABASE_LOCAL_SETUP.md +178 -0
- package/docs/development/TESTING.md +714 -0
- package/docs/feature-architectures/LOADING_ARCHITECTURE.md +343 -0
- package/docs/feature-architectures/NOTIFICATION_ARCHITECTURE.md +858 -0
- package/docs/feature-architectures/RATE_LIMIT_RESET.md +147 -0
- package/docs/feature-architectures/RBAC.md +1132 -0
- package/docs/feature-architectures/RESOURCE_LIMITS.md +69 -0
- package/docs/feature-architectures/SECURITY.md +284 -0
- package/docs/feature-architectures/WORKSPACES.md +278 -0
- package/docs/plans/admin-setup-wizard-routing-plan.md +623 -0
- package/drizzle/0000_purple_wilson_fisk.sql +360 -0
- package/drizzle/0001_drop_instance_public_url.sql +1 -0
- package/drizzle/meta/0000_snapshot.json +3118 -0
- package/drizzle/meta/_journal.json +20 -0
- package/drizzle.config.ts +13 -0
- package/eslint.config.mjs +44 -0
- package/install.sh +180 -0
- package/next.config.ts +91 -0
- package/package.json +128 -22
- package/playwright.config.ts +70 -0
- package/postcss.config.mjs +7 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.svg +11 -0
- package/public/next.svg +1 -0
- package/public/playground/CPM-101/as-is-image.jpg +0 -0
- package/public/playground/CPM-101/to-be-image.jpg +0 -0
- package/public/playground/TEST-1/LinkedIn-skeleton-screen.png +0 -0
- package/public/playground/TEST-1/https___dev-to-uploads.s3.amazonaws.com_uploads_articles_vuahe90ka1mkx9aepmea.webp +0 -0
- package/public/playground/TEST-1/linkedin_skeletonscreen.jpg +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/scripts/__tests__/migrate.integration.test.ts +642 -0
- package/scripts/__tests__/migrate.property.test.ts +1714 -0
- package/scripts/__tests__/migrate.test.ts +536 -0
- package/scripts/admin-reset-password.ts +114 -0
- package/scripts/check-email-queue.ts +99 -0
- package/scripts/check-sessions.ts +50 -0
- package/scripts/db-pull-data.sh +73 -0
- package/scripts/force-verify-email.sh +13 -0
- package/scripts/migrate.ts +693 -0
- package/scripts/process-email-queue.ts +26 -0
- package/scripts/reset-db.ts +47 -0
- package/scripts/reset-rate-limit.sh +26 -0
- package/scripts/reset-remote-db.sql +31 -0
- package/scripts/retry-failed-emails.ts +67 -0
- package/scripts/seed.ts +605 -0
- package/scripts/setup-monitoring.sh +440 -0
- package/scripts/sync-migration-tracking.ts +113 -0
- package/scripts/test-ci-error-handling.sh +237 -0
- package/scripts/test-ci-workflow.sh +200 -0
- package/scripts/test-migration.sh +151 -0
- package/scripts/validate-env.ts +25 -0
- package/scripts/validate-migration-system.ts +566 -0
- package/scripts/verify-ci-status-reporting.sh +206 -0
- package/scripts/verify-user-email.sql +22 -0
- package/scripts/verify-vercel-integration.ts +292 -0
- package/seed_data.md +54 -0
- package/src/app/(protected)/(team)/(routes)/[projectSlug]/error.tsx +89 -0
- package/src/app/(protected)/(team)/(routes)/[projectSlug]/loading.tsx +101 -0
- package/src/app/(protected)/(team)/(routes)/[projectSlug]/page.tsx +91 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/README.md +192 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/error.tsx +58 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/loading.tsx +14 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/not-found.tsx +47 -0
- package/src/app/(protected)/(team)/(routes)/issue/[issueKey]/page.tsx +91 -0
- package/src/app/(protected)/(team)/projects/page.tsx +16 -0
- package/src/app/(protected)/(team)/team/settings/(section)/instance/page.tsx +52 -0
- package/src/app/(protected)/(team)/team/settings/(section)/integrations/loading.tsx +5 -0
- package/src/app/(protected)/(team)/team/settings/(section)/integrations/page.tsx +23 -0
- package/src/app/(protected)/(team)/team/settings/(section)/members/loading.tsx +5 -0
- package/src/app/(protected)/(team)/team/settings/(section)/members/page.tsx +35 -0
- package/src/app/(protected)/(team)/team/settings/layout.tsx +72 -0
- package/src/app/(protected)/(team)/team/settings/loading.tsx +5 -0
- package/src/app/(protected)/(team)/team/settings/page.tsx +71 -0
- package/src/app/(protected)/dev/auth/README.md +151 -0
- package/src/app/(protected)/dev/auth/page.tsx +590 -0
- package/src/app/(protected)/layout.test.tsx +209 -0
- package/src/app/(protected)/layout.tsx +28 -0
- package/src/app/(protected)/onboarding/page.tsx +27 -0
- package/src/app/(protected)/settings/integrations/page.tsx +23 -0
- package/src/app/(protected)/settings/layout.tsx +26 -0
- package/src/app/(protected)/settings/notifications/page.tsx +26 -0
- package/src/app/(protected)/settings/other/page.tsx +23 -0
- package/src/app/(protected)/settings/page.tsx +23 -0
- package/src/app/(protected)/settings/preferences/page.tsx +23 -0
- package/src/app/(protected)/settings/security/page.tsx +37 -0
- package/src/app/(public)/forgot-password/page.tsx +20 -0
- package/src/app/(public)/invite/project/[token]/error.tsx +50 -0
- package/src/app/(public)/invite/project/[token]/loading.tsx +39 -0
- package/src/app/(public)/invite/project/[token]/page.tsx +156 -0
- package/src/app/(public)/layout.tsx +9 -0
- package/src/app/(public)/privacy-policy/page.tsx +12 -0
- package/src/app/(public)/reset-password/page.tsx +37 -0
- package/src/app/(public)/setup/__tests__/page.test.tsx +30 -0
- package/src/app/(public)/setup/page.tsx +17 -0
- package/src/app/(public)/share/issue/[token]/page.tsx +51 -0
- package/src/app/(public)/sign-in/page.tsx +55 -0
- package/src/app/(public)/sign-up/page.tsx +23 -0
- package/src/app/(public)/verify-email/page.tsx +22 -0
- package/src/app/(public)/verify-email-confirm/page.tsx +40 -0
- package/src/app/api/auth/[...all]/route.ts +6 -0
- package/src/app/api/auth/delete-account/route.ts +134 -0
- package/src/app/api/auth/dev/force-verify/route.ts +180 -0
- package/src/app/api/auth/dev/reset-rate-limit/route.ts +144 -0
- package/src/app/api/auth/dev/sessions/route.ts +172 -0
- package/src/app/api/auth/forgot-password/__tests__/forgot-password.property.test.ts +397 -0
- package/src/app/api/auth/forgot-password/route.ts +277 -0
- package/src/app/api/auth/logout/route.ts +115 -0
- package/src/app/api/auth/me/route.ts +123 -0
- package/src/app/api/auth/providers/__tests__/route.test.ts +236 -0
- package/src/app/api/auth/providers/route.ts +119 -0
- package/src/app/api/auth/resend-verification/route.ts +262 -0
- package/src/app/api/auth/reset-password/__tests__/reset-password.property.test.ts +493 -0
- package/src/app/api/auth/reset-password/__tests__/route.test.ts +284 -0
- package/src/app/api/auth/reset-password/route.ts +251 -0
- package/src/app/api/auth/verify-email/route.ts +232 -0
- package/src/app/api/example-cors/route.ts +61 -0
- package/src/app/api/health/route.ts +14 -0
- package/src/app/api/invite/project/[token]/__tests__/accept-invitation.integration.test.ts +348 -0
- package/src/app/api/invite/project/[token]/decline/route.ts +99 -0
- package/src/app/api/invite/project/[token]/route.ts +269 -0
- package/src/app/api/issues/[issueId]/activities/route.ts +213 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/[commentId]/route.ts +486 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/comments/route.ts +283 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/read/route.ts +242 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/[annotationId]/route.ts +534 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/annotations/route.ts +514 -0
- package/src/app/api/issues/[issueId]/attachments/[attachmentId]/route.ts +161 -0
- package/src/app/api/issues/[issueId]/attachments/route.ts +376 -0
- package/src/app/api/issues/[issueId]/route.ts +516 -0
- package/src/app/api/notifications/[id]/read/route.ts +131 -0
- package/src/app/api/notifications/__tests__/notifications.integration.test.ts +350 -0
- package/src/app/api/notifications/read-all/route.ts +72 -0
- package/src/app/api/notifications/route.ts +148 -0
- package/src/app/api/notifications/unread-count/route.ts +77 -0
- package/src/app/api/projects/[id]/activities/route.ts +174 -0
- package/src/app/api/projects/[id]/invitations/[invitationId]/resend/route.ts +99 -0
- package/src/app/api/projects/[id]/invitations/[invitationId]/route.ts +96 -0
- package/src/app/api/projects/[id]/invitations/route.ts +254 -0
- package/src/app/api/projects/[id]/issues/route.ts +452 -0
- package/src/app/api/projects/[id]/join/route.ts +207 -0
- package/src/app/api/projects/[id]/members/[memberId]/route.ts +364 -0
- package/src/app/api/projects/[id]/members/me/route.ts +121 -0
- package/src/app/api/projects/[id]/members/route.ts +129 -0
- package/src/app/api/projects/[id]/route.ts +476 -0
- package/src/app/api/projects/route.ts +394 -0
- package/src/app/api/setup/admin/route.ts +255 -0
- package/src/app/api/setup/complete/__tests__/route.test.ts +60 -0
- package/src/app/api/setup/complete/route.ts +244 -0
- package/src/app/api/setup/config/route.ts +195 -0
- package/src/app/api/setup/export/route.ts +111 -0
- package/src/app/api/setup/health/route.ts +74 -0
- package/src/app/api/setup/import/route.ts +154 -0
- package/src/app/api/setup/status/route.ts +82 -0
- package/src/app/api/setup/workspace/route.ts +252 -0
- package/src/app/api/teams/[teamId]/export/route.ts +115 -0
- package/src/app/api/teams/[teamId]/invitations/[invitationId]/resend/route.ts +132 -0
- package/src/app/api/teams/[teamId]/invitations/[invitationId]/route.ts +117 -0
- package/src/app/api/teams/[teamId]/invitations/route.ts +363 -0
- package/src/app/api/teams/[teamId]/members/[userId]/route.ts +335 -0
- package/src/app/api/teams/[teamId]/members/route.ts +184 -0
- package/src/app/api/teams/[teamId]/members/search/route.ts +202 -0
- package/src/app/api/teams/[teamId]/route.ts +423 -0
- package/src/app/api/teams/[teamId]/switch/route.ts +140 -0
- package/src/app/api/teams/[teamId]/transfer-ownership/route.ts +212 -0
- package/src/app/api/teams/invitations/[token]/accept/route.ts +140 -0
- package/src/app/api/teams/invitations/by-id/[id]/accept/route.ts +98 -0
- package/src/app/api/teams/invitations/by-id/[id]/decline/route.ts +90 -0
- package/src/app/api/teams/route.ts +278 -0
- package/src/app/api/uploads/media/route.ts +118 -0
- package/src/app/api/uploads/presigned/route.ts +49 -0
- package/src/app/api/user/linked-accounts/route.ts +35 -0
- package/src/app/email-preview/page.tsx +11 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/global-error.tsx +21 -0
- package/src/app/layout.tsx +50 -0
- package/src/app/page.tsx +5 -0
- package/src/components/icons/atlassian-icon.tsx +22 -0
- package/src/components/icons/index.ts +1 -0
- package/src/components/layout/SIDEBAR_LAYOUT_BEST_PRACTICES.md +240 -0
- package/src/components/layout/app-shell-header-store.tsx +20 -0
- package/src/components/layout/app-shell-skeleton.tsx +89 -0
- package/src/components/layout/app-shell-wrapper.tsx +32 -0
- package/src/components/layout/app-shell.test.tsx +155 -0
- package/src/components/layout/app-shell.tsx +100 -0
- package/src/components/shared/headers/app-header-configurator.tsx +42 -0
- package/src/components/shared/headers/app-header.tsx +103 -0
- package/src/components/shared/headers/header-user-menu.tsx +247 -0
- package/src/components/shared/headers/index.ts +44 -0
- package/src/components/shared/headers/page-header.tsx +25 -0
- package/src/components/shared/notifications/__tests__/notification-bell.test.tsx +159 -0
- package/src/components/shared/notifications/__tests__/notification-dropdown.test.tsx +296 -0
- package/src/components/shared/notifications/__tests__/notification-item.test.tsx +328 -0
- package/src/components/shared/notifications/index.ts +45 -0
- package/src/components/shared/notifications/notification-actions.tsx +295 -0
- package/src/components/shared/notifications/notification-bell-button.tsx +77 -0
- package/src/components/shared/notifications/notification-dropdown.tsx +160 -0
- package/src/components/shared/notifications/notification-group-item.tsx +268 -0
- package/src/components/shared/notifications/notification-item.tsx +193 -0
- package/src/components/shared/notifications/notification-load-more.tsx +50 -0
- package/src/components/shared/notifications/notification-panel.tsx +49 -0
- package/src/components/shared/notifications/utils.tsx +127 -0
- package/src/components/shared/permission-guard/index.ts +1 -0
- package/src/components/shared/permission-guard/permission-tooltip.tsx +45 -0
- package/src/components/shared/relative-time.tsx +53 -0
- package/src/components/shared/section-container.tsx +32 -0
- package/src/components/shared/service-status-banner.tsx +121 -0
- package/src/components/shared/settings-sidebar/index.ts +2 -0
- package/src/components/shared/settings-sidebar/team-setting-aside.tsx +97 -0
- package/src/components/shared/settings-sidebar/user-settings-aside.tsx +66 -0
- package/src/components/shared/sidebar/app-sidebar.tsx +146 -0
- package/src/components/shared/sidebar/index.ts +36 -0
- package/src/components/shared/sidebar/sidebar-main.tsx +81 -0
- package/src/components/shared/sidebar/sidebar-project.tsx +61 -0
- package/src/components/shared/sidebar/sidebar-team-avatar.tsx +126 -0
- package/src/components/shared/sidebar/sidebar-team-switcher.tsx +185 -0
- package/src/components/shared/sidebar/type.ts +97 -0
- package/src/components/ui/alert-dialog.tsx +157 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/avatar-upload.tsx +147 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +244 -0
- package/src/components/ui/image-cropper-dialog.tsx +167 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/optimized-image.tsx +220 -0
- package/src/components/ui/pagination.tsx +127 -0
- package/src/components/ui/popover.tsx +48 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +187 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/components/ui/sidebar.tsx +733 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/sonner.tsx +40 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +66 -0
- package/src/components/ui/textarea.tsx +23 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/config/__tests__/workspace.property.test.ts +40 -0
- package/src/config/auth.ts +62 -0
- package/src/config/integrations.ts +126 -0
- package/src/config/quotas.ts +20 -0
- package/src/config/roles.ts +463 -0
- package/src/config/settings-nav.ts +39 -0
- package/src/config/team-settings-nav.ts +37 -0
- package/src/config/user-settings-nav.ts +42 -0
- package/src/config/version.ts +1 -0
- package/src/config/workspace.ts +64 -0
- package/src/features/annotations/README.md +283 -0
- package/src/features/annotations/api/annotations-api.ts +194 -0
- package/src/features/annotations/api/comments-api.ts +147 -0
- package/src/features/annotations/api/index.ts +71 -0
- package/src/features/annotations/api/save-annotation.ts +150 -0
- package/src/features/annotations/api/schemas.ts +142 -0
- package/src/features/annotations/components/annotated-attachment-view.tsx +576 -0
- package/src/features/annotations/components/annotation-action-sheet.tsx +140 -0
- package/src/features/annotations/components/annotation-annotations-panel.tsx +213 -0
- package/src/features/annotations/components/annotation-box.tsx +539 -0
- package/src/features/annotations/components/annotation-canvas.tsx +534 -0
- package/src/features/annotations/components/annotation-comment-input.tsx +145 -0
- package/src/features/annotations/components/annotation-context-menu.tsx +164 -0
- package/src/features/annotations/components/annotation-drawer.tsx +231 -0
- package/src/features/annotations/components/annotation-layer.tsx +271 -0
- package/src/features/annotations/components/annotation-pin.tsx +318 -0
- package/src/features/annotations/components/annotation-popover.tsx +562 -0
- package/src/features/annotations/components/annotation-thread-panel.tsx +485 -0
- package/src/features/annotations/components/annotation-thread-preview.tsx +195 -0
- package/src/features/annotations/components/annotation-toolbar.tsx +244 -0
- package/src/features/annotations/components/keyboard-shortcuts-modal.tsx +79 -0
- package/src/features/annotations/docs/ANNOTATIONS_ARCHITECTURE.md +67 -0
- package/src/features/annotations/docs/ANNOTATION_SAVE_ARCHITECTURE.md +422 -0
- package/src/features/annotations/docs/ANNOTATION_SAVE_FEATURE.md +408 -0
- package/src/features/annotations/docs/BOX_ANNOTATION_GUIDE.md +542 -0
- package/src/features/annotations/docs/NEXTSTEP.md +28 -0
- package/src/features/annotations/docs/STALE_CLOSURE_FIX.md +344 -0
- package/src/features/annotations/docs/UNDO_REDO_QUICK_START.md +545 -0
- package/src/features/annotations/docs/local_first_canvas_autosave_architecture.md +674 -0
- package/src/features/annotations/examples/complete-example.tsx +266 -0
- package/src/features/annotations/examples/save-annotation-example.tsx +309 -0
- package/src/features/annotations/hooks/__tests__/use-annotation-permissions.property.test.tsx +493 -0
- package/src/features/annotations/hooks/index.ts +36 -0
- package/src/features/annotations/hooks/use-annotation-batch-save.ts +109 -0
- package/src/features/annotations/hooks/use-annotation-comments.ts +353 -0
- package/src/features/annotations/hooks/use-annotation-drafts.ts +137 -0
- package/src/features/annotations/hooks/use-annotation-edit-state.ts +99 -0
- package/src/features/annotations/hooks/use-annotation-history-tracker.ts +159 -0
- package/src/features/annotations/hooks/use-annotation-integration.ts +916 -0
- package/src/features/annotations/hooks/use-annotation-permissions.ts +210 -0
- package/src/features/annotations/hooks/use-annotation-popover.ts +175 -0
- package/src/features/annotations/hooks/use-annotation-save.ts +208 -0
- package/src/features/annotations/hooks/use-annotation-tools.ts +237 -0
- package/src/features/annotations/hooks/use-annotations-with-history.ts +332 -0
- package/src/features/annotations/hooks/use-auto-save.ts +94 -0
- package/src/features/annotations/index.ts +111 -0
- package/src/features/annotations/types/annotation.ts +201 -0
- package/src/features/annotations/types/index.ts +28 -0
- package/src/features/annotations/utils/history-manager.ts +73 -0
- package/src/features/annotations/utils/index.ts +2 -0
- package/src/features/annotations/utils/map-attachments-to-threads.ts +28 -0
- package/src/features/annotations/utils/position-comment-input.ts +136 -0
- package/src/features/annotations/utils/re-sequence-labels.ts +92 -0
- package/src/features/annotations/utils/validate-annotation-label.ts +120 -0
- package/src/features/auth/api/types.ts +101 -0
- package/src/features/auth/components/__tests__/role-gate.test.tsx +448 -0
- package/src/features/auth/components/__tests__/social-login-buttons.test.tsx +313 -0
- package/src/features/auth/components/auth-card.tsx +36 -0
- package/src/features/auth/components/forgot-password-form.tsx +115 -0
- package/src/features/auth/components/index.ts +14 -0
- package/src/features/auth/components/invite-code-input.tsx +155 -0
- package/src/features/auth/components/invited-user-form.tsx +309 -0
- package/src/features/auth/components/onboarding-form.tsx +195 -0
- package/src/features/auth/components/password-strength-indicator.tsx +113 -0
- package/src/features/auth/components/reset-password-form.tsx +138 -0
- package/src/features/auth/components/role-gate.tsx +124 -0
- package/src/features/auth/components/self-registration-choice.tsx +153 -0
- package/src/features/auth/components/sign-in-form.tsx +159 -0
- package/src/features/auth/components/sign-up-form.tsx +158 -0
- package/src/features/auth/components/social-login-buttons.tsx +219 -0
- package/src/features/auth/hooks/__tests__/use-onboarding.test.tsx +109 -0
- package/src/features/auth/hooks/__tests__/use-session.test.tsx +160 -0
- package/src/features/auth/hooks/index.ts +15 -0
- package/src/features/auth/hooks/use-accept-invitation.ts +194 -0
- package/src/features/auth/hooks/use-delete-account.ts +86 -0
- package/src/features/auth/hooks/use-force-verify.ts +89 -0
- package/src/features/auth/hooks/use-forgot-password.ts +144 -0
- package/src/features/auth/hooks/use-link-account.ts +78 -0
- package/src/features/auth/hooks/use-linked-accounts.ts +88 -0
- package/src/features/auth/hooks/use-onboarding.ts +159 -0
- package/src/features/auth/hooks/use-resend-verification.ts +139 -0
- package/src/features/auth/hooks/use-reset-password.ts +151 -0
- package/src/features/auth/hooks/use-reset-rate-limit.ts +56 -0
- package/src/features/auth/hooks/use-self-registration.ts +202 -0
- package/src/features/auth/hooks/use-session.ts +81 -0
- package/src/features/auth/hooks/use-sessions.ts +59 -0
- package/src/features/auth/hooks/use-sign-in.ts +234 -0
- package/src/features/auth/hooks/use-sign-out.ts +88 -0
- package/src/features/auth/hooks/use-sign-up.ts +194 -0
- package/src/features/auth/hooks/use-unlink-account.ts +100 -0
- package/src/features/auth/hooks/use-verify-email-token.ts +125 -0
- package/src/features/auth/index.ts +75 -0
- package/src/features/auth/screens/forgot-password-screen.tsx +33 -0
- package/src/features/auth/screens/index.ts +7 -0
- package/src/features/auth/screens/onboarding-screen.tsx +49 -0
- package/src/features/auth/screens/reset-password-screen.tsx +33 -0
- package/src/features/auth/screens/sign-in-screen.tsx +61 -0
- package/src/features/auth/screens/sign-up-screen.tsx +37 -0
- package/src/features/auth/screens/verify-email-confirm-screen.tsx +286 -0
- package/src/features/auth/screens/verify-email-screen.tsx +146 -0
- package/src/features/auth/types/index.ts +14 -0
- package/src/features/auth/utils/__tests__/validators.test.ts +331 -0
- package/src/features/auth/utils/password-strength.ts +129 -0
- package/src/features/auth/utils/validators.ts +124 -0
- package/src/features/email-preview/actions/render-email.ts +21 -0
- package/src/features/email-preview/screens/email-preview-screen.tsx +81 -0
- package/src/features/folder-scaffold-template/index.ts +0 -0
- package/src/features/instance-settings/components/index.ts +6 -0
- package/src/features/instance-settings/components/instance-settings-form.tsx +180 -0
- package/src/features/instance-settings/components/instance-status-display.tsx +158 -0
- package/src/features/instance-settings/index.ts +7 -0
- package/src/features/instance-settings/screens/index.ts +5 -0
- package/src/features/instance-settings/screens/instance-settings-screen.tsx +59 -0
- package/src/features/issues/README.md +330 -0
- package/src/features/issues/api/create-issue.ts +19 -0
- package/src/features/issues/api/delete-issue.ts +27 -0
- package/src/features/issues/api/get-issue-activities.ts +58 -0
- package/src/features/issues/api/get-issue-details.ts +25 -0
- package/src/features/issues/api/get-project-issues-server.ts +44 -0
- package/src/features/issues/api/get-project-issues.ts +21 -0
- package/src/features/issues/api/index.ts +44 -0
- package/src/features/issues/api/update-issue.ts +31 -0
- package/src/features/issues/api/upload-attachment.ts +81 -0
- package/src/features/issues/components/activity-timeline.tsx +440 -0
- package/src/features/issues/components/canvas-state-indicator.tsx +90 -0
- package/src/features/issues/components/centered-canvas-view.tsx +739 -0
- package/src/features/issues/components/image-selector.tsx +123 -0
- package/src/features/issues/components/image-upload-zone.tsx +262 -0
- package/src/features/issues/components/infinite-canvas-background.tsx +163 -0
- package/src/features/issues/components/inline-editable-select.tsx +173 -0
- package/src/features/issues/components/inline-editable-text.tsx +225 -0
- package/src/features/issues/components/inline-editable-textarea.tsx +219 -0
- package/src/features/issues/components/inline-editable-user-select.tsx +202 -0
- package/src/features/issues/components/issue-deletion-dialog.tsx +142 -0
- package/src/features/issues/components/issue-details-panel.tsx +101 -0
- package/src/features/issues/components/issues-create-dialog.tsx +578 -0
- package/src/features/issues/components/issues-list-filter.tsx +312 -0
- package/src/features/issues/components/issues-list.tsx +151 -0
- package/src/features/issues/components/issues-priority-badge.tsx +77 -0
- package/src/features/issues/components/issues-status-badge.tsx +100 -0
- package/src/features/issues/components/metadata-section.tsx +389 -0
- package/src/features/issues/components/optimized-attachment-view.tsx +528 -0
- package/src/features/issues/components/optimized-image.tsx +257 -0
- package/src/features/issues/components/panel-header.tsx +186 -0
- package/src/features/issues/components/preload.ts +31 -0
- package/src/features/issues/components/priority-selector.tsx +101 -0
- package/src/features/issues/components/responsive-issue-layout-skeleton.tsx +139 -0
- package/src/features/issues/components/responsive-issue-layout.tsx +617 -0
- package/src/features/issues/components/status-selector.tsx +320 -0
- package/src/features/issues/components/type-selector.tsx +102 -0
- package/src/features/issues/components/upload-progress-overlay.tsx +35 -0
- package/src/features/issues/components/uploaded-image-preview.tsx +173 -0
- package/src/features/issues/components/workflow-control.tsx +318 -0
- package/src/features/issues/components/zoom-controls.tsx +150 -0
- package/src/features/issues/config/index.ts +47 -0
- package/src/features/issues/config/options.ts +323 -0
- package/src/features/issues/config/workflow.ts +102 -0
- package/src/features/issues/docs/ARCHITECTURE_DIAGRAM.md +321 -0
- package/src/features/issues/docs/BACKEND_ARCHITECTURE.md +194 -0
- package/src/features/issues/docs/IMAGE_COMPONENTS_ARCHITECTURE.md +363 -0
- package/src/features/issues/docs/IMAGE_COMPONENTS_IMPORTS.md +412 -0
- package/src/features/issues/docs/IMPLEMENTATION_CHECKLIST.md +210 -0
- package/src/features/issues/docs/ROUTE_SETUP_COMPLETE.md +242 -0
- package/src/features/issues/hooks/index.ts +78 -0
- package/src/features/issues/hooks/use-canvas-transform.ts +255 -0
- package/src/features/issues/hooks/use-create-issue.ts +28 -0
- package/src/features/issues/hooks/use-elastic-scroll.ts +296 -0
- package/src/features/issues/hooks/use-issue-activities.ts +71 -0
- package/src/features/issues/hooks/use-issue-delete.ts +84 -0
- package/src/features/issues/hooks/use-issue-details.ts +70 -0
- package/src/features/issues/hooks/use-issue-filters.ts +50 -0
- package/src/features/issues/hooks/use-issue-update.ts +93 -0
- package/src/features/issues/hooks/use-keyboard-shortcuts.ts +104 -0
- package/src/features/issues/hooks/use-optimized-image.ts +228 -0
- package/src/features/issues/hooks/use-project-issues.ts +14 -0
- package/src/features/issues/index.ts +65 -0
- package/src/features/issues/screens/issue-details-screen.tsx +207 -0
- package/src/features/issues/screens/issue-details-skeletons.tsx +295 -0
- package/src/features/issues/screens/issue-share-screen.tsx +56 -0
- package/src/features/issues/types/index.ts +48 -0
- package/src/features/issues/types/issue.ts +291 -0
- package/src/features/issues/utils/filter-issues.ts +141 -0
- package/src/features/issues/utils/index.ts +14 -0
- package/src/features/legal/index.ts +1 -0
- package/src/features/legal/screens/privacy-policy-screen.tsx +307 -0
- package/src/features/notifications/api/get-notifications.ts +58 -0
- package/src/features/notifications/api/get-unread-count.ts +37 -0
- package/src/features/notifications/api/index.ts +35 -0
- package/src/features/notifications/api/mark-all-as-read.ts +37 -0
- package/src/features/notifications/api/mark-as-read.ts +41 -0
- package/src/features/notifications/api/types.ts +109 -0
- package/src/features/notifications/hooks/__tests__/use-notification-subscription.test.ts +206 -0
- package/src/features/notifications/hooks/index.ts +28 -0
- package/src/features/notifications/hooks/use-mark-all-as-read.ts +106 -0
- package/src/features/notifications/hooks/use-mark-as-read.ts +106 -0
- package/src/features/notifications/hooks/use-notification-subscription.ts +244 -0
- package/src/features/notifications/hooks/use-notification-toast.ts +161 -0
- package/src/features/notifications/hooks/use-notifications.ts +80 -0
- package/src/features/notifications/hooks/use-unread-count.ts +60 -0
- package/src/features/notifications/index.ts +48 -0
- package/src/features/notifications/utils/group-notifications.ts +152 -0
- package/src/features/notifications/utils/index.ts +9 -0
- package/src/features/projects/api/create-invitation.ts +45 -0
- package/src/features/projects/api/create-project.ts +64 -0
- package/src/features/projects/api/delete-project.ts +50 -0
- package/src/features/projects/api/get-project-activities.ts +43 -0
- package/src/features/projects/api/get-project-members.ts +53 -0
- package/src/features/projects/api/get-project.ts +49 -0
- package/src/features/projects/api/get-projects.ts +61 -0
- package/src/features/projects/api/index.ts +27 -0
- package/src/features/projects/api/join-project.ts +52 -0
- package/src/features/projects/api/leave-project.ts +51 -0
- package/src/features/projects/api/list-invitations.ts +36 -0
- package/src/features/projects/api/remove-member.ts +60 -0
- package/src/features/projects/api/resend-invitation.ts +36 -0
- package/src/features/projects/api/revoke-invitation.ts +36 -0
- package/src/features/projects/api/types.ts +286 -0
- package/src/features/projects/api/update-member-role.ts +70 -0
- package/src/features/projects/api/update-project.ts +69 -0
- package/src/features/projects/components/__tests__/project-detail-activity-feed.test.tsx +106 -0
- package/src/features/projects/components/__tests__/project-invitation-dialog.test.tsx +211 -0
- package/src/features/projects/components/__tests__/project-member-manager-dialog.test.tsx +254 -0
- package/src/features/projects/components/index.ts +21 -0
- package/src/features/projects/components/project-actions.tsx +248 -0
- package/src/features/projects/components/project-create-dialog.tsx +410 -0
- package/src/features/projects/components/project-detail-activity-feed.tsx +206 -0
- package/src/features/projects/components/project-detail-header.tsx +103 -0
- package/src/features/projects/components/project-detail-overview.tsx +128 -0
- package/src/features/projects/components/project-icon-selector.test.tsx +49 -0
- package/src/features/projects/components/project-icon-selector.tsx +76 -0
- package/src/features/projects/components/project-invitation-dialog.tsx +368 -0
- package/src/features/projects/components/project-issues.tsx +128 -0
- package/src/features/projects/components/project-leave-button.tsx +69 -0
- package/src/features/projects/components/project-list-card.tsx +246 -0
- package/src/features/projects/components/project-list-filters.tsx +320 -0
- package/src/features/projects/components/project-member-manager-dialog.tsx +419 -0
- package/src/features/projects/components/project-settings-dialog.tsx +204 -0
- package/src/features/projects/components/project-stats.tsx +46 -0
- package/src/features/projects/components/project-title-section.tsx +78 -0
- package/src/features/projects/config/icons.ts +91 -0
- package/src/features/projects/hooks/index.ts +28 -0
- package/src/features/projects/hooks/use-create-invitation.ts +83 -0
- package/src/features/projects/hooks/use-create-project.ts +77 -0
- package/src/features/projects/hooks/use-delete-project.ts +84 -0
- package/src/features/projects/hooks/use-join-project.ts +43 -0
- package/src/features/projects/hooks/use-leave-project.ts +84 -0
- package/src/features/projects/hooks/use-project-activities.ts +39 -0
- package/src/features/projects/hooks/use-project-filters.ts +86 -0
- package/src/features/projects/hooks/use-project-invitations.ts +66 -0
- package/src/features/projects/hooks/use-project-members.ts +57 -0
- package/src/features/projects/hooks/use-project.ts +67 -0
- package/src/features/projects/hooks/use-projects.ts +49 -0
- package/src/features/projects/hooks/use-recent-projects.ts +58 -0
- package/src/features/projects/hooks/use-remove-member.ts +89 -0
- package/src/features/projects/hooks/use-resend-invitation.ts +68 -0
- package/src/features/projects/hooks/use-revoke-invitation.ts +71 -0
- package/src/features/projects/hooks/use-team-member-suggestions.ts +133 -0
- package/src/features/projects/hooks/use-update-member-role.ts +92 -0
- package/src/features/projects/hooks/use-update-project.ts +88 -0
- package/src/features/projects/index.ts +91 -0
- package/src/features/projects/screens/index.ts +3 -0
- package/src/features/projects/screens/invitation-acceptance-screen.tsx +320 -0
- package/src/features/projects/screens/project-detail-screen-wrapper.tsx +47 -0
- package/src/features/projects/screens/project-detail-screen.tsx +661 -0
- package/src/features/projects/screens/projects-list-screen.tsx +161 -0
- package/src/features/projects/types/index.ts +59 -0
- package/src/features/projects/utils/format-helpers.ts +16 -0
- package/src/features/projects/utils/index.ts +2 -0
- package/src/features/projects/utils/role-helpers.ts +25 -0
- package/src/features/setup/api/complete-setup.ts +21 -0
- package/src/features/setup/api/create-admin.ts +21 -0
- package/src/features/setup/api/create-first-workspace.ts +21 -0
- package/src/features/setup/api/get-instance-status.ts +12 -0
- package/src/features/setup/api/get-service-health.ts +12 -0
- package/src/features/setup/api/index.ts +44 -0
- package/src/features/setup/api/save-instance-config.ts +21 -0
- package/src/features/setup/api/types.ts +122 -0
- package/src/features/setup/components/__tests__/setup-wizard-ui.test.tsx +362 -0
- package/src/features/setup/components/admin-account-step.tsx +205 -0
- package/src/features/setup/components/first-workspace-step.tsx +120 -0
- package/src/features/setup/components/index.ts +9 -0
- package/src/features/setup/components/instance-config-step.tsx +107 -0
- package/src/features/setup/components/mail-config-step.tsx +205 -0
- package/src/features/setup/components/sample-data-step.tsx +131 -0
- package/src/features/setup/components/service-health-step.tsx +180 -0
- package/src/features/setup/components/service-status-badge.tsx +50 -0
- package/src/features/setup/components/setup-progress.tsx +103 -0
- package/src/features/setup/components/setup-wizard.tsx +169 -0
- package/src/features/setup/hooks/index.ts +12 -0
- package/src/features/setup/hooks/use-complete-setup.ts +23 -0
- package/src/features/setup/hooks/use-create-admin.ts +25 -0
- package/src/features/setup/hooks/use-create-first-workspace.ts +24 -0
- package/src/features/setup/hooks/use-instance-status.ts +21 -0
- package/src/features/setup/hooks/use-save-instance-config.ts +23 -0
- package/src/features/setup/hooks/use-service-health.ts +21 -0
- package/src/features/setup/hooks/use-setup-wizard.ts +152 -0
- package/src/features/setup/hooks/use-workspace-mode.ts +19 -0
- package/src/features/setup/index.ts +30 -0
- package/src/features/setup/screens/index.ts +1 -0
- package/src/features/setup/screens/setup-screen.tsx +40 -0
- package/src/features/setup/types/index.ts +69 -0
- package/src/features/setup/utils/index.ts +78 -0
- package/src/features/team-settings/components/index.ts +39 -0
- package/src/features/team-settings/components/loading-states.tsx +296 -0
- package/src/features/team-settings/components/permission-guard.tsx +23 -0
- package/src/features/team-settings/components/settings-card.tsx +22 -0
- package/src/features/team-settings/components/settings-context-provider.tsx +51 -0
- package/src/features/team-settings/components/settings-error-boundary.tsx +366 -0
- package/src/features/team-settings/components/settings-navigation.tsx +87 -0
- package/src/features/team-settings/components/settings-section.tsx +23 -0
- package/src/features/team-settings/components/team-danger-zone.tsx +275 -0
- package/src/features/team-settings/components/team-information-form.tsx +116 -0
- package/src/features/team-settings/components/team-invitations-list.tsx +463 -0
- package/src/features/team-settings/components/team-members-list.tsx +342 -0
- package/src/features/team-settings/components/team-permission-guard.tsx +56 -0
- package/src/features/team-settings/components/team-setting-integrations.tsx +27 -0
- package/src/features/team-settings/components/team-setting-member.tsx +28 -0
- package/src/features/team-settings/components/team-settings-general.tsx +131 -0
- package/src/features/team-settings/components/transfer-ownership-modal.tsx +164 -0
- package/src/features/team-settings/components/unauthorized-access.tsx +52 -0
- package/src/features/team-settings/hooks/__tests__/use-team-settings.test.tsx +139 -0
- package/src/features/team-settings/hooks/index.ts +1 -0
- package/src/features/team-settings/hooks/use-team-settings.ts +148 -0
- package/src/features/team-settings/hooks/use-transfer-ownership.ts +45 -0
- package/src/features/team-settings/index.ts +25 -0
- package/src/features/team-settings/screens/index.ts +1 -0
- package/src/features/team-settings/screens/team-settings-screen.tsx +33 -0
- package/src/features/team-settings/types/index.ts +78 -0
- package/src/features/team-settings/utils/index.ts +6 -0
- package/src/features/team-settings/utils/mock-data.ts +40 -0
- package/src/features/teams/api/cancel-invitation.ts +25 -0
- package/src/features/teams/api/create-invitation.ts +28 -0
- package/src/features/teams/api/create-team.ts +14 -0
- package/src/features/teams/api/delete-team.ts +19 -0
- package/src/features/teams/api/get-invitations.ts +25 -0
- package/src/features/teams/api/get-team-members.ts +25 -0
- package/src/features/teams/api/get-team.ts +13 -0
- package/src/features/teams/api/get-teams.ts +19 -0
- package/src/features/teams/api/index.ts +49 -0
- package/src/features/teams/api/leave-team.ts +22 -0
- package/src/features/teams/api/remove-member.ts +25 -0
- package/src/features/teams/api/resend-invitation.ts +26 -0
- package/src/features/teams/api/switch-team.ts +13 -0
- package/src/features/teams/api/types.ts +122 -0
- package/src/features/teams/api/update-member-roles.ts +28 -0
- package/src/features/teams/api/update-team.ts +17 -0
- package/src/features/teams/components/create-team-dialog.tsx +105 -0
- package/src/features/teams/hooks/__tests__/cache-invalidation.property.test.tsx +268 -0
- package/src/features/teams/hooks/index.ts +35 -0
- package/src/features/teams/hooks/use-can-manage-members.ts +21 -0
- package/src/features/teams/hooks/use-can-manage-team.ts +21 -0
- package/src/features/teams/hooks/use-cancel-invitation.ts +52 -0
- package/src/features/teams/hooks/use-create-invitation.ts +59 -0
- package/src/features/teams/hooks/use-create-team.ts +38 -0
- package/src/features/teams/hooks/use-delete-team.ts +43 -0
- package/src/features/teams/hooks/use-invitations.ts +31 -0
- package/src/features/teams/hooks/use-leave-team.ts +43 -0
- package/src/features/teams/hooks/use-remove-member.ts +58 -0
- package/src/features/teams/hooks/use-resend-invitation.ts +52 -0
- package/src/features/teams/hooks/use-switch-team.ts +41 -0
- package/src/features/teams/hooks/use-team-members.ts +31 -0
- package/src/features/teams/hooks/use-team-permissions.ts +102 -0
- package/src/features/teams/hooks/use-team.ts +30 -0
- package/src/features/teams/hooks/use-teams.ts +26 -0
- package/src/features/teams/hooks/use-update-member-roles.ts +64 -0
- package/src/features/teams/hooks/use-update-team.ts +47 -0
- package/src/features/teams/index.ts +111 -0
- package/src/features/user-settings/actions/set-password.ts +63 -0
- package/src/features/user-settings/api/index.ts +16 -0
- package/src/features/user-settings/components/__tests__/security-settings.test.tsx +125 -0
- package/src/features/user-settings/components/delete-account-dialog.tsx +185 -0
- package/src/features/user-settings/components/index.ts +13 -0
- package/src/features/user-settings/components/integrations-list.tsx +152 -0
- package/src/features/user-settings/components/notification-preferences.tsx +112 -0
- package/src/features/user-settings/components/other-settings.tsx +126 -0
- package/src/features/user-settings/components/password-section.tsx +297 -0
- package/src/features/user-settings/components/security-settings.tsx +184 -0
- package/src/features/user-settings/components/user-preferences.tsx +146 -0
- package/src/features/user-settings/hooks/index.ts +8 -0
- package/src/features/user-settings/hooks/use-notification-preferences.ts +65 -0
- package/src/features/user-settings/hooks/use-user-preferences.ts +52 -0
- package/src/features/user-settings/index.ts +22 -0
- package/src/features/user-settings/screens/index.ts +11 -0
- package/src/features/user-settings/screens/integrations-screen.tsx +24 -0
- package/src/features/user-settings/screens/notifications-screen.tsx +29 -0
- package/src/features/user-settings/screens/other-settings-screen.tsx +24 -0
- package/src/features/user-settings/screens/setting-preferences-screen.tsx +24 -0
- package/src/features/user-settings/screens/user-settings-screen.tsx +23 -0
- package/src/features/user-settings/types/index.ts +42 -0
- package/src/features/user-settings/utils/index.ts +8 -0
- package/src/hooks/use-long-press.ts +196 -0
- package/src/hooks/use-media-upload.ts +95 -0
- package/src/hooks/use-mobile.ts +19 -0
- package/src/hooks/use-team.ts +32 -0
- package/src/instrumentation.ts +14 -0
- package/src/lib/__tests__/auth-config.test.ts +166 -0
- package/src/lib/__tests__/config.test.ts +42 -0
- package/src/lib/__tests__/db.test.ts +101 -0
- package/src/lib/__tests__/env.property.test.ts +197 -0
- package/src/lib/__tests__/env.test.ts +177 -0
- package/src/lib/__tests__/health-check.test.ts +87 -0
- package/src/lib/__tests__/oauth-errors.test.ts +184 -0
- package/src/lib/__tests__/oauth-redirect.test.ts +224 -0
- package/src/lib/__tests__/oauth-scopes.test.ts +268 -0
- package/src/lib/__tests__/storage.test.ts +58 -0
- package/src/lib/__tests__/url-validator.test.ts +232 -0
- package/src/lib/api-client.ts +93 -0
- package/src/lib/auth-client.ts +5 -0
- package/src/lib/auth-config.ts +146 -0
- package/src/lib/auth.ts +209 -0
- package/src/lib/config.ts +228 -0
- package/src/lib/cors.ts +96 -0
- package/src/lib/date.ts +16 -0
- package/src/lib/db.ts +88 -0
- package/src/lib/env.ts +489 -0
- package/src/lib/feedback.ts +29 -0
- package/src/lib/health-check.ts +229 -0
- package/src/lib/logger.ts +204 -0
- package/src/lib/oauth-errors.ts +138 -0
- package/src/lib/performance.ts +367 -0
- package/src/lib/query-server.ts +35 -0
- package/src/lib/query.tsx +43 -0
- package/src/lib/resend.ts +36 -0
- package/src/lib/security-headers.ts +165 -0
- package/src/lib/setup-status.ts +34 -0
- package/src/lib/storage.ts +150 -0
- package/src/lib/supabase-client.ts +58 -0
- package/src/lib/testing/test-db.ts +75 -0
- package/src/lib/url-validator.ts +168 -0
- package/src/lib/utils.ts +6 -0
- package/src/mocks/README.md +42 -0
- package/src/mocks/activity.fixtures.ts +281 -0
- package/src/mocks/annotation.fixtures.ts +325 -0
- package/src/mocks/attachment.fixtures.ts +80 -0
- package/src/mocks/email.fixtures.ts +61 -0
- package/src/mocks/index.ts +33 -0
- package/src/mocks/issue.fixtures.ts +364 -0
- package/src/mocks/landing.fixtures.ts +118 -0
- package/src/mocks/notification.fixtures.ts +111 -0
- package/src/mocks/project-activity.fixtures.ts +69 -0
- package/src/mocks/project-invitation.fixtures.ts +95 -0
- package/src/mocks/project-member.fixtures.ts +93 -0
- package/src/mocks/project.fixtures.ts +249 -0
- package/src/mocks/settings.fixtures.ts +56 -0
- package/src/mocks/share.fixtures.ts +48 -0
- package/src/mocks/team-member.fixtures.ts +147 -0
- package/src/mocks/team.fixtures.ts +35 -0
- package/src/mocks/user-settings.fixtures.ts +77 -0
- package/src/mocks/user.fixtures.ts +21 -0
- package/src/providers/theme-provider.tsx +11 -0
- package/src/proxy/__tests__/proxy-setup-gate.test.ts +43 -0
- package/src/proxy.ts +169 -0
- package/src/server/annotations/__tests__/annotation-limit.property.test.ts +291 -0
- package/src/server/annotations/__tests__/attachment-deletion-cascade.property.test.ts +385 -0
- package/src/server/annotations/__tests__/comment-delete-authorization.property.test.ts +419 -0
- package/src/server/annotations/__tests__/sanitize.property.test.ts +302 -0
- package/src/server/annotations/annotation-service.ts +305 -0
- package/src/server/annotations/comment-service.ts +288 -0
- package/src/server/annotations/index.ts +61 -0
- package/src/server/annotations/permission-utils.ts +125 -0
- package/src/server/annotations/sanitize.ts +134 -0
- package/src/server/annotations/types.ts +161 -0
- package/src/server/audit/audit-service.ts +85 -0
- package/src/server/audit/index.ts +11 -0
- package/src/server/audit/types.ts +75 -0
- package/src/server/auth/__tests__/README.md +368 -0
- package/src/server/auth/__tests__/account-linking.integration.test.ts +410 -0
- package/src/server/auth/__tests__/auth-integration.test.ts +811 -0
- package/src/server/auth/__tests__/cookies.test.ts +337 -0
- package/src/server/auth/__tests__/login.property.test.ts +428 -0
- package/src/server/auth/__tests__/oauth-integration.test.ts +555 -0
- package/src/server/auth/__tests__/password.test.ts +194 -0
- package/src/server/auth/__tests__/rate-limiter.test.ts +450 -0
- package/src/server/auth/__tests__/rbac.test.ts +474 -0
- package/src/server/auth/__tests__/session.test.ts +599 -0
- package/src/server/auth/__tests__/signup.property.test.ts +224 -0
- package/src/server/auth/__tests__/token-encryption.test.ts +171 -0
- package/src/server/auth/__tests__/tokens.test.ts +476 -0
- package/src/server/auth/__tests__/verify-email.property.test.ts +372 -0
- package/src/server/auth/cookies.ts +184 -0
- package/src/server/auth/password.ts +94 -0
- package/src/server/auth/rate-limiter.ts +257 -0
- package/src/server/auth/rbac.ts +1168 -0
- package/src/server/auth/session.ts +392 -0
- package/src/server/auth/token-encryption.ts +201 -0
- package/src/server/auth/tokens.ts +397 -0
- package/src/server/db/schema/account.ts +21 -0
- package/src/server/db/schema/annotation-read-status.ts +57 -0
- package/src/server/db/schema/better-auth-verifications.ts +27 -0
- package/src/server/db/schema/email-jobs.ts +24 -0
- package/src/server/db/schema/index.ts +20 -0
- package/src/server/db/schema/instance-settings.ts +42 -0
- package/src/server/db/schema/issue-activities.ts +97 -0
- package/src/server/db/schema/issue-attachments.ts +101 -0
- package/src/server/db/schema/issues.ts +139 -0
- package/src/server/db/schema/notifications.ts +119 -0
- package/src/server/db/schema/project-activities.ts +116 -0
- package/src/server/db/schema/project-invitations.ts +34 -0
- package/src/server/db/schema/project-members.ts +29 -0
- package/src/server/db/schema/projects.ts +46 -0
- package/src/server/db/schema/sessions.ts +17 -0
- package/src/server/db/schema/team-invitations.ts +22 -0
- package/src/server/db/schema/team-members.ts +18 -0
- package/src/server/db/schema/teams.ts +15 -0
- package/src/server/db/schema/user-roles.ts +14 -0
- package/src/server/db/schema/users.ts +17 -0
- package/src/server/db/schema/verification-tokens.ts +15 -0
- package/src/server/email/README.md +258 -0
- package/src/server/email/__tests__/client.property.test.ts +218 -0
- package/src/server/email/__tests__/payload.property.test.ts +205 -0
- package/src/server/email/__tests__/queue.test.ts +407 -0
- package/src/server/email/__tests__/render-template.test.tsx +172 -0
- package/src/server/email/client.ts +70 -0
- package/src/server/email/index.ts +20 -0
- package/src/server/email/providers/console-provider.ts +43 -0
- package/src/server/email/providers/index.ts +5 -0
- package/src/server/email/providers/resend-provider.ts +59 -0
- package/src/server/email/providers/smtp-provider.ts +65 -0
- package/src/server/email/queue.ts +365 -0
- package/src/server/email/render-template.tsx +117 -0
- package/src/server/email/templates/layout.tsx +66 -0
- package/src/server/email/templates/ownership-transfer-email.tsx +75 -0
- package/src/server/email/templates/password-reset-email.tsx +72 -0
- package/src/server/email/templates/project-invitation-email.tsx +70 -0
- package/src/server/email/templates/security-alert-email.tsx +161 -0
- package/src/server/email/templates/team-invitation-email.tsx +68 -0
- package/src/server/email/templates/verification-email.tsx +61 -0
- package/src/server/email/templates/welcome-email.tsx +60 -0
- package/src/server/email/worker.ts +182 -0
- package/src/server/issues/activity-service.ts +422 -0
- package/src/server/issues/attachment-service.ts +379 -0
- package/src/server/issues/index.ts +68 -0
- package/src/server/issues/issue-service.ts +569 -0
- package/src/server/issues/types.ts +233 -0
- package/src/server/notifications/__tests__/notification-service.integration.test.ts +405 -0
- package/src/server/notifications/index.ts +13 -0
- package/src/server/notifications/notification-service.ts +526 -0
- package/src/server/notifications/types.ts +234 -0
- package/src/server/projects/__tests__/activity-logging.integration.test.ts +319 -0
- package/src/server/projects/__tests__/invitation-email.integration.test.ts +258 -0
- package/src/server/projects/__tests__/invitation-service.integration.test.ts +651 -0
- package/src/server/projects/__tests__/member-service.property.test.ts +397 -0
- package/src/server/projects/__tests__/project-service.property.test.ts +116 -0
- package/src/server/projects/activity-service.ts +548 -0
- package/src/server/projects/index.ts +11 -0
- package/src/server/projects/invitation-service.ts +773 -0
- package/src/server/projects/member-service.ts +458 -0
- package/src/server/projects/project-service.ts +491 -0
- package/src/server/projects/schemas.ts +310 -0
- package/src/server/projects/types.ts +166 -0
- package/src/server/projects/utils.ts +128 -0
- package/src/server/setup/__tests__/health-check.property.test.ts +70 -0
- package/src/server/setup/__tests__/sample-data.property.test.ts +82 -0
- package/src/server/setup/__tests__/setup.property.test.ts +95 -0
- package/src/server/setup/health-check-service.ts +294 -0
- package/src/server/setup/index.ts +35 -0
- package/src/server/setup/sample-data-service.ts +233 -0
- package/src/server/setup/setup-service.ts +229 -0
- package/src/server/setup/types.ts +96 -0
- package/src/server/teams/__tests__/export.property.test.ts +542 -0
- package/src/server/teams/__tests__/get-members.integration.test.ts +105 -0
- package/src/server/teams/__tests__/invitation-flow.integration.test.ts +402 -0
- package/src/server/teams/__tests__/invitations.property.test.ts +235 -0
- package/src/server/teams/__tests__/member-management-flow.integration.test.ts +306 -0
- package/src/server/teams/__tests__/members.property.test.ts +180 -0
- package/src/server/teams/__tests__/ownership-transfer.property.test.ts +173 -0
- package/src/server/teams/__tests__/resource-limits.property.test.ts +382 -0
- package/src/server/teams/__tests__/team-context-management.integration.test.ts +396 -0
- package/src/server/teams/__tests__/team-context.property.test.ts +854 -0
- package/src/server/teams/__tests__/team-creation-flow.integration.test.ts +310 -0
- package/src/server/teams/__tests__/team-creation.property.test.ts +280 -0
- package/src/server/teams/errors.ts +396 -0
- package/src/server/teams/export-service.ts +383 -0
- package/src/server/teams/index.ts +10 -0
- package/src/server/teams/invitation-service.ts +708 -0
- package/src/server/teams/member-service.ts +334 -0
- package/src/server/teams/resource-limits.ts +211 -0
- package/src/server/teams/slug.ts +58 -0
- package/src/server/teams/team-context.ts +648 -0
- package/src/server/teams/team-service.ts +660 -0
- package/src/server/teams/types.ts +81 -0
- package/src/server/teams/validation.ts +209 -0
- package/src/styles/globals.css +160 -0
- package/src/types/deployment.ts +39 -0
- package/supabase/config.toml +357 -0
- package/supabase/seed.sql +480 -0
- package/tests/e2e/.gitkeep +0 -0
- package/tests/e2e/QUICK_START.md +98 -0
- package/tests/e2e/README.md +301 -0
- package/tests/e2e/auth.spec.ts +583 -0
- package/tests/e2e/global-setup.ts +23 -0
- package/tests/e2e/global-teardown.ts +23 -0
- package/tests/e2e/helpers/auth-helpers.ts +310 -0
- package/tests/e2e/helpers/test-fixtures.ts +286 -0
- package/tests/e2e/smoke-test.spec.ts +330 -0
- package/tsconfig.json +48 -0
- package/vitest.config.ts +50 -0
- package/vitest.setup.ts +56 -0
- package/dist/index.js +0 -778
|
@@ -0,0 +1,858 @@
|
|
|
1
|
+
# Notification Architecture
|
|
2
|
+
|
|
3
|
+
## 1. Overview
|
|
4
|
+
|
|
5
|
+
The Notification system provides real-time alerts to users about critical workspace activities. It uses a **pull-based architecture with real-time updates** via Supabase Realtime, ensuring users stay informed about collaboration updates (comments, mentions), workflow changes (assignments, status updates), and system access events (invitations).
|
|
6
|
+
|
|
7
|
+
**Key characteristics:**
|
|
8
|
+
- Real-time notification delivery with polling fallback
|
|
9
|
+
- Actor self-notification prevention
|
|
10
|
+
- Deduplication to prevent spam
|
|
11
|
+
- Client-side grouping for better UX
|
|
12
|
+
- Deep-link navigation to relevant entities
|
|
13
|
+
- Row-Level Security for data protection
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## 2. Architecture & Data Flow
|
|
18
|
+
|
|
19
|
+
### A. How Notifications Are Created
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
sequenceDiagram
|
|
23
|
+
participant User as User (Actor)
|
|
24
|
+
participant Service as Service Layer
|
|
25
|
+
participant DB as Database
|
|
26
|
+
participant RT as Supabase Realtime
|
|
27
|
+
participant Recipient as Recipient Client
|
|
28
|
+
|
|
29
|
+
User->>Service: Performs action (e.g., createComment)
|
|
30
|
+
Service->>Service: Check shouldCreateNotification()<br/>(actor exclusion)
|
|
31
|
+
Service->>Service: Check isDuplicate()<br/>(deduplication)
|
|
32
|
+
Service->>DB: INSERT into notifications
|
|
33
|
+
DB->>RT: Broadcast INSERT event
|
|
34
|
+
RT->>Recipient: Push notification payload
|
|
35
|
+
Recipient->>Recipient: Show Toast + Update Badge
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Fire-and-forget pattern:** Notification creation does NOT block the triggering action. If notification creation fails, the error is logged but the main operation continues.
|
|
39
|
+
|
|
40
|
+
### B. Real-time Updates with Fallback
|
|
41
|
+
|
|
42
|
+
1. **Primary:** Supabase Realtime subscription via `useNotificationSubscription` hook
|
|
43
|
+
2. **Fallback:** If Realtime connection is lost, automatically switches to polling `/api/notifications/unread-count` every 30 seconds
|
|
44
|
+
|
|
45
|
+
#### Supabase Realtime Implementation
|
|
46
|
+
|
|
47
|
+
The `useNotificationSubscription` hook (`src/features/notifications/hooks/use-notification-subscription.ts`) provides:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
const { isConnected, isPolling, error, reconnect } = useNotificationSubscription({
|
|
51
|
+
userId: user?.id,
|
|
52
|
+
enabled: !!user && !!currentTeam,
|
|
53
|
+
onNewNotification: (notification) => {
|
|
54
|
+
// Optional callback for custom handling
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Features:**
|
|
60
|
+
- Subscribes to `postgres_changes` INSERT events on `notifications` table
|
|
61
|
+
- Filters by `recipient_id=eq.{userId}` for security
|
|
62
|
+
- Automatically invalidates React Query cache on new notifications
|
|
63
|
+
- Falls back to polling if connection fails
|
|
64
|
+
- Provides `reconnect()` function for manual retry
|
|
65
|
+
|
|
66
|
+
**Connection States:**
|
|
67
|
+
- `isConnected: true` → Realtime active, polling disabled
|
|
68
|
+
- `isPolling: true` → Realtime failed, polling every 30s
|
|
69
|
+
- Both `false` → Disconnected (e.g., no user logged in)
|
|
70
|
+
|
|
71
|
+
#### Supabase Client Setup
|
|
72
|
+
|
|
73
|
+
The browser client (`src/lib/supabase-client.ts`) uses:
|
|
74
|
+
- `NEXT_PUBLIC_SUPABASE_URL` (or `SUPABASE_URL` for SSR)
|
|
75
|
+
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` (or `SUPABASE_ANON_KEY` for SSR)
|
|
76
|
+
|
|
77
|
+
**Important:** Only the anon key is used client-side. RLS policies ensure users only receive their own notifications.
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 3. Database Schema
|
|
82
|
+
|
|
83
|
+
### Notification Types
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
type NotificationType =
|
|
87
|
+
| 'mention' // User mentioned in comment
|
|
88
|
+
| 'comment_created' // Comment on assigned issue
|
|
89
|
+
| 'reply' // Reply to user's comment
|
|
90
|
+
| 'issue_assigned' // Issue assigned to user
|
|
91
|
+
| 'issue_status_changed' // Issue status changed
|
|
92
|
+
| 'project_invitation' // Invited to project
|
|
93
|
+
| 'team_invitation' // Invited to team
|
|
94
|
+
| 'role_updated' // User role changed
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
> **Note:** `issue_due_soon` is deferred to Phase 2 pending scheduled job infrastructure.
|
|
98
|
+
|
|
99
|
+
### Table Structure
|
|
100
|
+
|
|
101
|
+
```sql
|
|
102
|
+
create table notifications (
|
|
103
|
+
id uuid primary key default gen_random_uuid(),
|
|
104
|
+
recipient_id uuid not null references auth.users(id) on delete cascade,
|
|
105
|
+
actor_id uuid references auth.users(id) on delete set null,
|
|
106
|
+
type notification_type not null,
|
|
107
|
+
|
|
108
|
+
-- Polymorphic relation to entity
|
|
109
|
+
entity_type text not null, -- 'issue', 'project', 'comment', 'team'
|
|
110
|
+
entity_id uuid not null,
|
|
111
|
+
|
|
112
|
+
-- Denormalized metadata for rendering without extra fetches
|
|
113
|
+
metadata jsonb default '{}'::jsonb,
|
|
114
|
+
|
|
115
|
+
read_at timestamptz,
|
|
116
|
+
created_at timestamptz default now()
|
|
117
|
+
);
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Metadata Structure
|
|
121
|
+
|
|
122
|
+
The `metadata` JSONB field contains denormalized data for efficient rendering:
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
interface NotificationMetadata {
|
|
126
|
+
// Entity details
|
|
127
|
+
issue_title?: string;
|
|
128
|
+
issue_key?: string;
|
|
129
|
+
project_name?: string;
|
|
130
|
+
project_key?: string;
|
|
131
|
+
team_name?: string;
|
|
132
|
+
team_slug?: string;
|
|
133
|
+
|
|
134
|
+
// Navigation
|
|
135
|
+
target_url: string; // Deep-link URL (e.g., /teams/demo/projects/APP/issues/APP-123#comment-abc)
|
|
136
|
+
comment_id?: string; // For scroll-to-comment anchor
|
|
137
|
+
|
|
138
|
+
// Action data
|
|
139
|
+
invitation_id?: string; // For Accept/Decline actions
|
|
140
|
+
old_role?: string;
|
|
141
|
+
new_role?: string;
|
|
142
|
+
comment_preview?: string;
|
|
143
|
+
}
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Indexes
|
|
147
|
+
|
|
148
|
+
```sql
|
|
149
|
+
-- Unread notifications query (most common)
|
|
150
|
+
create index idx_notifications_recipient_unread
|
|
151
|
+
on notifications(recipient_id, created_at desc)
|
|
152
|
+
where read_at is null;
|
|
153
|
+
|
|
154
|
+
-- Entity lookup (for cleanup when entity is deleted)
|
|
155
|
+
create index idx_notifications_entity
|
|
156
|
+
on notifications(entity_type, entity_id);
|
|
157
|
+
|
|
158
|
+
-- Grouping queries (client-side grouping)
|
|
159
|
+
create index idx_notifications_grouping
|
|
160
|
+
on notifications(recipient_id, type, entity_type, entity_id, created_at desc);
|
|
161
|
+
|
|
162
|
+
-- Deduplication check (5-minute window)
|
|
163
|
+
create index idx_notifications_dedup
|
|
164
|
+
on notifications(recipient_id, actor_id, type, entity_type, entity_id, created_at desc);
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Row Level Security
|
|
168
|
+
|
|
169
|
+
```sql
|
|
170
|
+
alter table notifications enable row level security;
|
|
171
|
+
|
|
172
|
+
-- Users can only view their own notifications
|
|
173
|
+
create policy "Users can view own notifications"
|
|
174
|
+
on notifications for select
|
|
175
|
+
using (recipient_id = auth.uid());
|
|
176
|
+
|
|
177
|
+
-- Users can only update read status on their own notifications
|
|
178
|
+
create policy "Users can update own notifications"
|
|
179
|
+
on notifications for update
|
|
180
|
+
using (recipient_id = auth.uid())
|
|
181
|
+
with check (recipient_id = auth.uid());
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
> **Important:** Notification creation uses **service role** (server-side only) to bypass RLS.
|
|
185
|
+
|
|
186
|
+
---
|
|
187
|
+
|
|
188
|
+
## 4. Service Layer API
|
|
189
|
+
|
|
190
|
+
### Location
|
|
191
|
+
`src/server/notifications/notification-service.ts`
|
|
192
|
+
|
|
193
|
+
### Core Functions
|
|
194
|
+
|
|
195
|
+
#### `createNotification(data: CreateNotificationDTO)`
|
|
196
|
+
Creates a single notification with validation:
|
|
197
|
+
- **Actor exclusion:** Prevents users from receiving notifications for their own actions
|
|
198
|
+
- **Deduplication:** Prevents identical notifications within 5-minute window
|
|
199
|
+
- **Returns:** `Notification | null` (null if skipped due to exclusion/dedup)
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
interface CreateNotificationDTO {
|
|
203
|
+
recipientId: string;
|
|
204
|
+
actorId?: string;
|
|
205
|
+
type: NotificationType;
|
|
206
|
+
entityType: EntityType;
|
|
207
|
+
entityId: string;
|
|
208
|
+
metadata: NotificationMetadata;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### `createNotifications(data: CreateNotificationDTO[])`
|
|
213
|
+
Batch create for multi-recipient events (e.g., notifying all issue watchers).
|
|
214
|
+
|
|
215
|
+
#### `getNotifications(userId: string, options: PaginationOptions)`
|
|
216
|
+
Fetches paginated list of user's notifications, ordered by `created_at DESC`.
|
|
217
|
+
|
|
218
|
+
#### `markAsRead(notificationIds: string[])`
|
|
219
|
+
Marks specific notifications as read. Supports batch operations.
|
|
220
|
+
|
|
221
|
+
#### `markAllAsRead(userId: string)`
|
|
222
|
+
Marks all user's unread notifications as read.
|
|
223
|
+
|
|
224
|
+
#### `getUnreadCount(userId: string)`
|
|
225
|
+
Returns count of unread notifications. Used for badge display and polling fallback.
|
|
226
|
+
|
|
227
|
+
### Helper Functions
|
|
228
|
+
|
|
229
|
+
#### `buildTargetUrl(type: NotificationType, metadata: Partial<NotificationMetadata>)`
|
|
230
|
+
Generates deep-link URLs for navigation:
|
|
231
|
+
- **Issue notifications:** `/teams/{teamSlug}/projects/{projectKey}/issues/{issueKey}#comment-{commentId}`
|
|
232
|
+
- **Project invitations:** `/teams/{teamSlug}/projects/{projectKey}`
|
|
233
|
+
- **Team invitations:** `/teams/{teamSlug}`
|
|
234
|
+
|
|
235
|
+
#### `shouldCreateNotification(actorId: string, recipientId: string): boolean`
|
|
236
|
+
Returns `false` if `actorId === recipientId` (prevents self-notifications).
|
|
237
|
+
|
|
238
|
+
#### `isDuplicate(data: CreateNotificationDTO, windowMs: number): Promise<boolean>`
|
|
239
|
+
Checks if identical notification exists within the time window (default: 5 minutes).
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## 5. API Endpoints
|
|
244
|
+
|
|
245
|
+
| Method | Endpoint | Purpose | Performance Target |
|
|
246
|
+
|--------|----------|---------|-------------------|
|
|
247
|
+
| GET | `/api/notifications` | Paginated notification list | < 200ms for 50 notifications |
|
|
248
|
+
| GET | `/api/notifications/unread-count` | Lightweight unread count | < 100ms |
|
|
249
|
+
| PATCH | `/api/notifications/[id]/read` | Mark specific notification as read | < 100ms |
|
|
250
|
+
| POST | `/api/notifications/read-all` | Mark all notifications as read | < 200ms |
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## 6. Frontend Integration
|
|
255
|
+
|
|
256
|
+
### A. React Query Hooks
|
|
257
|
+
|
|
258
|
+
Location: `src/features/notifications/hooks/`
|
|
259
|
+
|
|
260
|
+
#### `useNotifications(options?: PaginationOptions)`
|
|
261
|
+
- Fetches paginated notifications
|
|
262
|
+
- Auto-updates cache when new notifications arrive via Realtime
|
|
263
|
+
- Implements fallback polling if Realtime disconnects
|
|
264
|
+
|
|
265
|
+
#### `useUnreadCount()`
|
|
266
|
+
- Fetches unread count for badge display
|
|
267
|
+
- Supports polling interval for fallback mode
|
|
268
|
+
|
|
269
|
+
#### `useNotificationSubscription(options)`
|
|
270
|
+
- **Primary Realtime hook** - subscribes to Supabase Realtime INSERT events
|
|
271
|
+
- Filters by `recipient_id` for security
|
|
272
|
+
- Invalidates React Query cache on new notifications
|
|
273
|
+
- Falls back to polling if connection fails
|
|
274
|
+
- Returns `{ isConnected, isPolling, error, reconnect }`
|
|
275
|
+
|
|
276
|
+
#### `useMarkAsRead()`
|
|
277
|
+
- Mutation for marking notifications as read
|
|
278
|
+
- Optimistic updates for instant UI feedback
|
|
279
|
+
|
|
280
|
+
#### `useMarkAllAsRead()`
|
|
281
|
+
- Mutation for marking all notifications as read
|
|
282
|
+
- Invalidates notification queries on success
|
|
283
|
+
|
|
284
|
+
#### `useNotificationToast()`
|
|
285
|
+
- Listens for React Query cache updates
|
|
286
|
+
- Triggers Sonner toast with notification preview for new notifications
|
|
287
|
+
|
|
288
|
+
#### `useGroupedNotifications()`
|
|
289
|
+
- Groups notifications by `(type, entity_type, entity_id)` for display
|
|
290
|
+
- Uses 1-hour time window by default
|
|
291
|
+
|
|
292
|
+
### B. UI Components
|
|
293
|
+
|
|
294
|
+
Location: `src/components/shared/notifications/`
|
|
295
|
+
|
|
296
|
+
#### `NotificationBell`
|
|
297
|
+
- Header icon with unread count badge
|
|
298
|
+
- Triggers dropdown on click
|
|
299
|
+
|
|
300
|
+
#### `NotificationDropdown`
|
|
301
|
+
- Popover list with grouped notifications
|
|
302
|
+
- Pagination support
|
|
303
|
+
- "Mark all as read" action
|
|
304
|
+
|
|
305
|
+
#### `NotificationItem`
|
|
306
|
+
- Polymorphic rendering based on notification type
|
|
307
|
+
- Click navigation to `metadata.target_url`
|
|
308
|
+
- Visual distinction for read/unread state
|
|
309
|
+
|
|
310
|
+
#### `NotificationGroupItem`
|
|
311
|
+
- Collapsed view for grouped notifications
|
|
312
|
+
- Shows "User A and 3 others commented on Issue #123"
|
|
313
|
+
|
|
314
|
+
#### `NotificationActions`
|
|
315
|
+
- Inline Accept/Decline buttons for invitation types
|
|
316
|
+
- Only renders for `project_invitation` and `team_invitation`
|
|
317
|
+
|
|
318
|
+
---
|
|
319
|
+
|
|
320
|
+
## 7. How to Integrate Notifications in Your Feature
|
|
321
|
+
|
|
322
|
+
```
|
|
323
|
+
┌─────────────────────────────────────────────────────────────────────────┐
|
|
324
|
+
│ NOTIFICATION INTEGRATION FLOW │
|
|
325
|
+
└─────────────────────────────────────────────────────────────────────────┘
|
|
326
|
+
|
|
327
|
+
Your Feature Service Notification Service Database
|
|
328
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────┐
|
|
329
|
+
│ │ │ │ │ │
|
|
330
|
+
│ 1. Main Action │ │ │ │ │
|
|
331
|
+
│ ──────────────►│ │ │ │ │
|
|
332
|
+
│ (e.g., create │──────┐ │ │ │ │
|
|
333
|
+
│ comment) │ │ │ │ │ │
|
|
334
|
+
│ │ │ │ │ │ │
|
|
335
|
+
│ │ │ │ │ │ │
|
|
336
|
+
│ 2. DB Insert │ │ │ │ │ │
|
|
337
|
+
│ ──────────────────────────────────────────────────────────────► │ INSERT │
|
|
338
|
+
│ │ │ │ │ │ comment │
|
|
339
|
+
│ │ │ │ │ │ │
|
|
340
|
+
│ │ │ │ │ │ │
|
|
341
|
+
│ 3. Fire-and- │ │ │ │ │ │
|
|
342
|
+
│ Forget │ └──────►│ createNotification() │ │
|
|
343
|
+
│ ──────────────────────────────┤ │ │ │
|
|
344
|
+
│ │ │ - Check actor │ │ │
|
|
345
|
+
│ │ │ exclusion │ │ │
|
|
346
|
+
│ │ │ - Check dedup │ │ │
|
|
347
|
+
│ │ │ - Build metadata │ │ │
|
|
348
|
+
│ │ │ ├─────────────►│ INSERT │
|
|
349
|
+
│ │ │ │ │ notif │
|
|
350
|
+
│ │ │ │ │ │
|
|
351
|
+
│ │ │ │ │ │
|
|
352
|
+
│ 4. Return │◄──────┐ │ │ │ │
|
|
353
|
+
│ ──────────────►│ │ │ │ │ │
|
|
354
|
+
│ (success) │ │ │ │ │ │
|
|
355
|
+
│ │ │ │ (async - no │ │ │
|
|
356
|
+
│ │ │ │ blocking) │ │ │
|
|
357
|
+
│ │ │ │ │ │ │
|
|
358
|
+
│ │ │ │ │ │ │
|
|
359
|
+
│ │ │ │ If error: │ │ │
|
|
360
|
+
│ │ │ │ - Log it │ │ │
|
|
361
|
+
│ │ └──────┤ - Don't throw │ │ │
|
|
362
|
+
│ │ │ │ │ │
|
|
363
|
+
└─────────────────┘ └──────────────────┘ └─────────┘
|
|
364
|
+
│
|
|
365
|
+
│ Realtime Broadcast
|
|
366
|
+
▼
|
|
367
|
+
┌─────────────┐
|
|
368
|
+
│ Recipient │
|
|
369
|
+
│ Client │
|
|
370
|
+
│ │
|
|
371
|
+
│ • Toast │
|
|
372
|
+
│ • Badge ↑ │
|
|
373
|
+
└─────────────┘
|
|
374
|
+
|
|
375
|
+
KEY PRINCIPLE: Notifications are FIRE-AND-FORGET
|
|
376
|
+
───────────────────────────────────────────────
|
|
377
|
+
✓ Main action completes successfully even if notification fails
|
|
378
|
+
✓ Notification errors are logged, not thrown
|
|
379
|
+
✓ User experience is never degraded by notification issues
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
### Step 1: Identify Triggering Events
|
|
383
|
+
|
|
384
|
+
Determine which user actions should create notifications:
|
|
385
|
+
- **Comments:** mentions, replies, comments on assigned issues
|
|
386
|
+
- **Issues:** assignments, status changes
|
|
387
|
+
- **Invitations:** project invitations, team invitations
|
|
388
|
+
- **Roles:** role updates
|
|
389
|
+
|
|
390
|
+
### Step 2: Import Notification Service
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { createNotification, createNotifications } from '@/server/notifications/notification-service'
|
|
394
|
+
import { buildTargetUrl } from '@/server/notifications/notification-service'
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Step 3: Create Notifications in Service Layer
|
|
398
|
+
|
|
399
|
+
**Example: Comment Service**
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
// src/server/annotations/comment-service.ts
|
|
403
|
+
import { createNotification, buildTargetUrl } from '@/server/notifications/notification-service'
|
|
404
|
+
|
|
405
|
+
export async function createComment(data: CreateCommentDTO) {
|
|
406
|
+
// 1. Create the comment
|
|
407
|
+
const comment = await db.insert(comments).values(data).returning()
|
|
408
|
+
|
|
409
|
+
// 2. Create notifications (fire-and-forget)
|
|
410
|
+
try {
|
|
411
|
+
// Notify mentioned users
|
|
412
|
+
const mentionedUserIds = extractMentions(data.content)
|
|
413
|
+
await createNotifications(
|
|
414
|
+
mentionedUserIds.map(userId => ({
|
|
415
|
+
recipientId: userId,
|
|
416
|
+
actorId: data.authorId,
|
|
417
|
+
type: 'mention',
|
|
418
|
+
entityType: 'comment',
|
|
419
|
+
entityId: comment.id,
|
|
420
|
+
metadata: {
|
|
421
|
+
issue_title: issue.title,
|
|
422
|
+
issue_key: issue.key,
|
|
423
|
+
comment_preview: data.content.substring(0, 100),
|
|
424
|
+
target_url: buildTargetUrl('mention', {
|
|
425
|
+
team_slug: team.slug,
|
|
426
|
+
project_key: project.key,
|
|
427
|
+
issue_key: issue.key,
|
|
428
|
+
comment_id: comment.id,
|
|
429
|
+
}),
|
|
430
|
+
comment_id: comment.id,
|
|
431
|
+
},
|
|
432
|
+
}))
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
// Notify issue assignee if comment is on their issue
|
|
436
|
+
if (issue.assigneeId && issue.assigneeId !== data.authorId) {
|
|
437
|
+
await createNotification({
|
|
438
|
+
recipientId: issue.assigneeId,
|
|
439
|
+
actorId: data.authorId,
|
|
440
|
+
type: 'comment_created',
|
|
441
|
+
entityType: 'issue',
|
|
442
|
+
entityId: issue.id,
|
|
443
|
+
metadata: { /* ... */ },
|
|
444
|
+
})
|
|
445
|
+
}
|
|
446
|
+
} catch (error) {
|
|
447
|
+
// Log but don't block
|
|
448
|
+
logger.error('Failed to create comment notifications', { error, commentId: comment.id })
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return comment
|
|
452
|
+
}
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
**Example: Issue Service**
|
|
456
|
+
|
|
457
|
+
```typescript
|
|
458
|
+
// src/server/issues/issue-service.ts
|
|
459
|
+
export async function assignIssue(issueId: string, assigneeId: string, actorId: string) {
|
|
460
|
+
// 1. Update the issue
|
|
461
|
+
await db.update(issues).set({ assigneeId }).where(eq(issues.id, issueId))
|
|
462
|
+
|
|
463
|
+
// 2. Create notification (fire-and-forget)
|
|
464
|
+
try {
|
|
465
|
+
await createNotification({
|
|
466
|
+
recipientId: assigneeId,
|
|
467
|
+
actorId,
|
|
468
|
+
type: 'issue_assigned',
|
|
469
|
+
entityType: 'issue',
|
|
470
|
+
entityId: issueId,
|
|
471
|
+
metadata: {
|
|
472
|
+
issue_title: issue.title,
|
|
473
|
+
issue_key: issue.key,
|
|
474
|
+
target_url: buildTargetUrl('issue_assigned', {
|
|
475
|
+
team_slug: team.slug,
|
|
476
|
+
project_key: project.key,
|
|
477
|
+
issue_key: issue.key,
|
|
478
|
+
}),
|
|
479
|
+
},
|
|
480
|
+
})
|
|
481
|
+
} catch (error) {
|
|
482
|
+
logger.error('Failed to create assignment notification', { error, issueId })
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**Example: Invitation Service**
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
// src/server/projects/invitation-service.ts
|
|
491
|
+
export async function createProjectInvitation(data: CreateInvitationDTO) {
|
|
492
|
+
// 1. Create the invitation
|
|
493
|
+
const invitation = await db.insert(projectInvitations).values(data).returning()
|
|
494
|
+
|
|
495
|
+
// 2. Create notification
|
|
496
|
+
try {
|
|
497
|
+
await createNotification({
|
|
498
|
+
recipientId: data.inviteeId,
|
|
499
|
+
actorId: data.inviterId,
|
|
500
|
+
type: 'project_invitation',
|
|
501
|
+
entityType: 'project',
|
|
502
|
+
entityId: data.projectId,
|
|
503
|
+
metadata: {
|
|
504
|
+
project_name: project.name,
|
|
505
|
+
project_key: project.key,
|
|
506
|
+
team_slug: team.slug,
|
|
507
|
+
invitation_id: invitation.id, // For Accept/Decline actions
|
|
508
|
+
target_url: buildTargetUrl('project_invitation', {
|
|
509
|
+
team_slug: team.slug,
|
|
510
|
+
project_key: project.key,
|
|
511
|
+
}),
|
|
512
|
+
},
|
|
513
|
+
})
|
|
514
|
+
} catch (error) {
|
|
515
|
+
logger.error('Failed to create invitation notification', { error, invitationId: invitation.id })
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return invitation
|
|
519
|
+
}
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
### Step 4: Test Notification Creation
|
|
523
|
+
|
|
524
|
+
**Integration Test Example:**
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
// __tests__/comment-service.integration.test.ts
|
|
528
|
+
describe('createComment', () => {
|
|
529
|
+
it('should create mention notification for mentioned user', async () => {
|
|
530
|
+
const comment = await createComment({
|
|
531
|
+
content: '@alice Please review this',
|
|
532
|
+
authorId: bob.id,
|
|
533
|
+
issueId: issue.id,
|
|
534
|
+
})
|
|
535
|
+
|
|
536
|
+
const notifications = await getNotifications(alice.id)
|
|
537
|
+
expect(notifications).toHaveLength(1)
|
|
538
|
+
expect(notifications[0]).toMatchObject({
|
|
539
|
+
type: 'mention',
|
|
540
|
+
actorId: bob.id,
|
|
541
|
+
entityType: 'comment',
|
|
542
|
+
entityId: comment.id,
|
|
543
|
+
})
|
|
544
|
+
})
|
|
545
|
+
|
|
546
|
+
it('should NOT create self-notification when user mentions themselves', async () => {
|
|
547
|
+
await createComment({
|
|
548
|
+
content: '@alice Self-reminder',
|
|
549
|
+
authorId: alice.id,
|
|
550
|
+
issueId: issue.id,
|
|
551
|
+
})
|
|
552
|
+
|
|
553
|
+
const notifications = await getNotifications(alice.id)
|
|
554
|
+
expect(notifications).toHaveLength(0) // Actor exclusion
|
|
555
|
+
})
|
|
556
|
+
})
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
---
|
|
560
|
+
|
|
561
|
+
## 8. Security Considerations
|
|
562
|
+
|
|
563
|
+
### A. Actor Self-Notification Prevention
|
|
564
|
+
|
|
565
|
+
The service layer prevents actors from receiving notifications for their own actions:
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
function shouldCreateNotification(actorId: string | undefined, recipientId: string): boolean {
|
|
569
|
+
if (actorId && actorId === recipientId) {
|
|
570
|
+
return false // Never notify actor of their own action
|
|
571
|
+
}
|
|
572
|
+
return true
|
|
573
|
+
}
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
**Examples:**
|
|
577
|
+
- User assigns issue to themselves → No notification
|
|
578
|
+
- User comments on their own issue → No notification
|
|
579
|
+
- User mentions themselves → No notification
|
|
580
|
+
|
|
581
|
+
### B. Deduplication
|
|
582
|
+
|
|
583
|
+
Prevents notification spam by checking for identical notifications within a 5-minute window:
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
async function isDuplicate(data: CreateNotificationDTO, windowMs: number = 5 * 60 * 1000): Promise<boolean> {
|
|
587
|
+
const cutoff = new Date(Date.now() - windowMs)
|
|
588
|
+
|
|
589
|
+
const existing = await db
|
|
590
|
+
.select({ id: notifications.id })
|
|
591
|
+
.from(notifications)
|
|
592
|
+
.where(
|
|
593
|
+
and(
|
|
594
|
+
eq(notifications.recipientId, data.recipientId),
|
|
595
|
+
eq(notifications.actorId, data.actorId ?? null),
|
|
596
|
+
eq(notifications.type, data.type),
|
|
597
|
+
eq(notifications.entityType, data.entityType),
|
|
598
|
+
eq(notifications.entityId, data.entityId),
|
|
599
|
+
gt(notifications.createdAt, cutoff)
|
|
600
|
+
)
|
|
601
|
+
)
|
|
602
|
+
.limit(1)
|
|
603
|
+
|
|
604
|
+
return existing.length > 0
|
|
605
|
+
}
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
### C. Row-Level Security
|
|
609
|
+
|
|
610
|
+
- Users can only `SELECT` and `UPDATE` their own notifications
|
|
611
|
+
- Notification creation uses service role (server-side only)
|
|
612
|
+
- All API endpoints validate session and enforce `recipient_id = auth.uid()`
|
|
613
|
+
|
|
614
|
+
### D. Input Validation
|
|
615
|
+
|
|
616
|
+
- All API inputs validated using Zod schemas
|
|
617
|
+
- `metadata` JSONB sanitized to prevent injection attacks
|
|
618
|
+
- Notification types restricted to enum values
|
|
619
|
+
|
|
620
|
+
---
|
|
621
|
+
|
|
622
|
+
## 9. Advanced Features
|
|
623
|
+
|
|
624
|
+
### A. Client-Side Grouping
|
|
625
|
+
|
|
626
|
+
Notifications are grouped by `(type, entity_type, entity_id)` within a time window (default: 1 hour) for better UX:
|
|
627
|
+
|
|
628
|
+
**Before Grouping:**
|
|
629
|
+
- Alice commented on Issue #123
|
|
630
|
+
- Bob commented on Issue #123
|
|
631
|
+
- Carol commented on Issue #123
|
|
632
|
+
|
|
633
|
+
**After Grouping:**
|
|
634
|
+
- Alice, Bob and 1 other commented on Issue #123
|
|
635
|
+
|
|
636
|
+
**Implementation:**
|
|
637
|
+
|
|
638
|
+
```typescript
|
|
639
|
+
// src/features/notifications/utils/group-notifications.ts
|
|
640
|
+
export function groupNotifications(
|
|
641
|
+
notifications: Notification[],
|
|
642
|
+
windowMs: number = 3600000 // 1 hour
|
|
643
|
+
): NotificationGroup[] {
|
|
644
|
+
// Group by (type, entity_type, entity_id)
|
|
645
|
+
// Only group if notifications within windowMs
|
|
646
|
+
}
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
**Rationale:** Client-side grouping keeps the database schema simple and allows flexible UI experimentation without migrations.
|
|
650
|
+
|
|
651
|
+
### B. Deep-Link Navigation
|
|
652
|
+
|
|
653
|
+
When a user clicks a notification, the system navigates to `metadata.target_url`:
|
|
654
|
+
|
|
655
|
+
**Issue notifications:**
|
|
656
|
+
```
|
|
657
|
+
/teams/{teamSlug}/projects/{projectKey}/issues/{issueKey}#comment-{commentId}
|
|
658
|
+
```
|
|
659
|
+
- Frontend scrolls to comment using `comment_id` from metadata
|
|
660
|
+
|
|
661
|
+
**Invitation notifications:**
|
|
662
|
+
```
|
|
663
|
+
/teams/{teamSlug}/projects/{projectKey}
|
|
664
|
+
```
|
|
665
|
+
- After acceptance, redirects to project
|
|
666
|
+
|
|
667
|
+
**Team invitation notifications:**
|
|
668
|
+
```
|
|
669
|
+
/teams/{teamSlug}
|
|
670
|
+
```
|
|
671
|
+
- After acceptance, redirects to team
|
|
672
|
+
|
|
673
|
+
### C. Inline Invitation Actions
|
|
674
|
+
|
|
675
|
+
For `project_invitation` and `team_invitation` types, the notification UI renders Accept/Decline buttons:
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
// NotificationActions component
|
|
679
|
+
if (notification.type === 'project_invitation') {
|
|
680
|
+
return (
|
|
681
|
+
<>
|
|
682
|
+
<Button onClick={() => acceptInvitation(notification.metadata.invitation_id)}>
|
|
683
|
+
Accept
|
|
684
|
+
</Button>
|
|
685
|
+
<Button variant="ghost" onClick={() => declineInvitation(notification.metadata.invitation_id)}>
|
|
686
|
+
Decline
|
|
687
|
+
</Button>
|
|
688
|
+
</>
|
|
689
|
+
)
|
|
690
|
+
}
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
---
|
|
694
|
+
|
|
695
|
+
## 10. Error Handling
|
|
696
|
+
|
|
697
|
+
| Scenario | Handling |
|
|
698
|
+
|----------|----------|
|
|
699
|
+
| Notification creation fails | Log error, do NOT block triggering action (fire-and-forget) |
|
|
700
|
+
| Realtime connection lost | Fall back to polling `unread-count` every 30s |
|
|
701
|
+
| Invalid notification type | Reject with 400 Bad Request, log for debugging |
|
|
702
|
+
| Missing recipient | Skip notification creation, log warning |
|
|
703
|
+
| Invitation already accepted/declined | Return 409 Conflict, notification remains but actions disabled |
|
|
704
|
+
|
|
705
|
+
---
|
|
706
|
+
|
|
707
|
+
## 11. Performance Targets
|
|
708
|
+
|
|
709
|
+
| Operation | Target | Strategy |
|
|
710
|
+
|-----------|--------|----------|
|
|
711
|
+
| Notification list API | < 200ms for 50 notifications | Optimized indexes, pagination |
|
|
712
|
+
| Unread count API | < 100ms | Indexed query on `recipient_id, read_at` |
|
|
713
|
+
| Notification creation | Non-blocking | Fire-and-forget pattern, logged errors |
|
|
714
|
+
| Realtime delivery | < 1 second | Supabase Realtime subscription |
|
|
715
|
+
| Polling fallback | 30-second intervals | Lightweight `/unread-count` endpoint |
|
|
716
|
+
|
|
717
|
+
---
|
|
718
|
+
|
|
719
|
+
## 12. Mobile Responsiveness
|
|
720
|
+
|
|
721
|
+
### Requirements
|
|
722
|
+
- Touch targets minimum 44px for all interactive elements
|
|
723
|
+
- Responsive dropdown layout for narrow viewports (< 480px)
|
|
724
|
+
- Notification items adapt content for small screens
|
|
725
|
+
|
|
726
|
+
### Implementation
|
|
727
|
+
- Use mobile-first CSS with media queries
|
|
728
|
+
- Scrollable notification list with pull-to-refresh (future)
|
|
729
|
+
|
|
730
|
+
---
|
|
731
|
+
|
|
732
|
+
## 13. Testing Strategy
|
|
733
|
+
|
|
734
|
+
### Unit Tests
|
|
735
|
+
- `buildTargetUrl()` generates correct URLs for all entity types
|
|
736
|
+
- `groupNotifications()` utility with various edge cases
|
|
737
|
+
- `NotificationItem` renders correct text/links for each type
|
|
738
|
+
|
|
739
|
+
### Integration Tests
|
|
740
|
+
- Service layer creates notifications correctly for different triggers
|
|
741
|
+
- Mark as read updates database state correctly
|
|
742
|
+
- Unread count query returns accurate results
|
|
743
|
+
- Actor exclusion prevents self-notifications
|
|
744
|
+
- Deduplication prevents spam
|
|
745
|
+
|
|
746
|
+
### Property-Based Tests (fast-check)
|
|
747
|
+
Each correctness property with minimum 100 iterations:
|
|
748
|
+
- **Property 1:** Notification creation correctness
|
|
749
|
+
- **Property 2:** Target URL generation
|
|
750
|
+
- **Property 4:** Unread count accuracy
|
|
751
|
+
- **Property 5:** Mark as read state change
|
|
752
|
+
- **Property 7:** Actor self-notification prevention
|
|
753
|
+
- **Property 8:** Deduplication correctness
|
|
754
|
+
|
|
755
|
+
### E2E Tests
|
|
756
|
+
- User A comments → User B receives notification and badge update
|
|
757
|
+
- Click notification → navigate to correct issue with comment scroll
|
|
758
|
+
- Invitation Accept/Decline flow updates notification state
|
|
759
|
+
- Realtime fallback activates when connection lost
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## 14. Future Enhancements (Deferred)
|
|
764
|
+
|
|
765
|
+
### Phase 2: Due Date Reminders
|
|
766
|
+
- Add `issue_due_soon` notification type
|
|
767
|
+
- Requires scheduled job infrastructure
|
|
768
|
+
- Daily check for issues approaching due date
|
|
769
|
+
|
|
770
|
+
### Phase 3: Email Integration
|
|
771
|
+
- Send email summaries for offline users
|
|
772
|
+
- Configurable email frequency (daily/weekly)
|
|
773
|
+
- Unsubscribe mechanism
|
|
774
|
+
|
|
775
|
+
### Phase 3: Notification Preferences
|
|
776
|
+
- User settings to control notification types
|
|
777
|
+
- Per-project notification settings
|
|
778
|
+
- "Do not disturb" mode
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
782
|
+
## 15. Quick Reference
|
|
783
|
+
|
|
784
|
+
### When to Create Notifications
|
|
785
|
+
|
|
786
|
+
| Event | Notification Type | Recipients |
|
|
787
|
+
|-------|------------------|------------|
|
|
788
|
+
| User mentioned in comment | `mention` | Mentioned user(s) |
|
|
789
|
+
| Reply to comment | `reply` | Original comment author |
|
|
790
|
+
| Comment on assigned issue | `comment_created` | Issue assignee |
|
|
791
|
+
| Issue assigned | `issue_assigned` | Assignee |
|
|
792
|
+
| Issue status changed | `issue_status_changed` | Assignee + Reporter |
|
|
793
|
+
| Project invitation sent | `project_invitation` | Invitee |
|
|
794
|
+
| Team invitation sent | `team_invitation` | Invitee |
|
|
795
|
+
| User role updated | `role_updated` | Affected user |
|
|
796
|
+
|
|
797
|
+
### Key Files
|
|
798
|
+
|
|
799
|
+
```
|
|
800
|
+
src/
|
|
801
|
+
├── lib/
|
|
802
|
+
│ └── supabase-client.ts # Supabase browser client (Realtime)
|
|
803
|
+
│
|
|
804
|
+
├── server/
|
|
805
|
+
│ └── notifications/
|
|
806
|
+
│ ├── types.ts # TypeScript types
|
|
807
|
+
│ ├── notification-service.ts # Core service layer
|
|
808
|
+
│ └── __tests__/
|
|
809
|
+
│ ├── notification-service.integration.test.ts
|
|
810
|
+
│ └── notification.property.test.ts
|
|
811
|
+
│
|
|
812
|
+
├── app/api/
|
|
813
|
+
│ └── notifications/
|
|
814
|
+
│ ├── route.ts # GET /api/notifications
|
|
815
|
+
│ ├── unread-count/route.ts # GET /api/notifications/unread-count
|
|
816
|
+
│ ├── [id]/read/route.ts # PATCH /api/notifications/[id]/read
|
|
817
|
+
│ └── read-all/route.ts # POST /api/notifications/read-all
|
|
818
|
+
│
|
|
819
|
+
├── features/notifications/
|
|
820
|
+
│ ├── api/ # API fetcher functions
|
|
821
|
+
│ ├── hooks/ # React Query hooks
|
|
822
|
+
│ │ ├── use-notifications.ts
|
|
823
|
+
│ │ ├── use-unread-count.ts
|
|
824
|
+
│ │ ├── use-mark-as-read.ts
|
|
825
|
+
│ │ ├── use-mark-all-as-read.ts
|
|
826
|
+
│ │ ├── use-notification-subscription.ts # Supabase Realtime subscription
|
|
827
|
+
│ │ ├── use-notification-toast.ts
|
|
828
|
+
│ │ └── index.ts # Barrel export
|
|
829
|
+
│ └── utils/
|
|
830
|
+
│ └── group-notifications.ts
|
|
831
|
+
│
|
|
832
|
+
└── components/shared/notifications/
|
|
833
|
+
├── notification-panel.tsx # Main entry point (integrates Realtime)
|
|
834
|
+
├── notification-bell-button.tsx
|
|
835
|
+
├── notification-dropdown.tsx
|
|
836
|
+
├── notification-item.tsx
|
|
837
|
+
├── notification-group-item.tsx
|
|
838
|
+
├── notification-actions.tsx
|
|
839
|
+
└── __tests__/
|
|
840
|
+
```
|
|
841
|
+
|
|
842
|
+
### Migration Files
|
|
843
|
+
|
|
844
|
+
```
|
|
845
|
+
supabase/migrations/
|
|
846
|
+
└── XXXXXX_create_notifications_table.sql
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
---
|
|
850
|
+
|
|
851
|
+
## Final Notes
|
|
852
|
+
|
|
853
|
+
- **Always use fire-and-forget** for notification creation to avoid blocking main operations
|
|
854
|
+
- **Never skip actor exclusion** — self-notifications are confusing UX
|
|
855
|
+
- **Denormalize metadata** to avoid N+1 queries when rendering notification lists
|
|
856
|
+
- **Use service role** for notification creation (bypasses RLS)
|
|
857
|
+
- **Test deduplication** to ensure spam prevention works correctly
|
|
858
|
+
- **Monitor performance** — notification queries should be fast (<200ms)
|