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,674 @@
|
|
|
1
|
+
# Local-First Canvas Autosave Architecture for UI SyncUp
|
|
2
|
+
|
|
3
|
+
> **Adapted for UI SyncUp** — A visual feedback and issue tracking platform for design-to-development collaboration.
|
|
4
|
+
|
|
5
|
+
This document defines the autosave architecture for the annotation canvas, designed to prevent the "saving loop overwrote my local drag state" bug while maintaining smooth, responsive UI during annotation editing.
|
|
6
|
+
|
|
7
|
+
## Goals
|
|
8
|
+
|
|
9
|
+
| Goal | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| **No Flicker** | Dragging/transforming an annotation stays perfectly stable while background saves happen |
|
|
12
|
+
| **Local-First** | UI always renders from local state, never from stale server responses |
|
|
13
|
+
| **Reliable Persistence** | Edits are acknowledged (ACK) by server without overwriting in-flight interactions |
|
|
14
|
+
| **React Query Compatible** | Works seamlessly with existing TanStack Query caching and optimistic updates |
|
|
15
|
+
| **Undo/Redo Safe** | History stack remains accurate across save cycles |
|
|
16
|
+
|
|
17
|
+
## Non-Goals (v1)
|
|
18
|
+
|
|
19
|
+
- Multi-user real-time collaboration (CRDT/OT) — future extension
|
|
20
|
+
- Offline-first across days — can be added with IndexedDB op log
|
|
21
|
+
- Full op log persistence on server — single-user ACK pattern sufficient
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Core Principle
|
|
26
|
+
|
|
27
|
+
> **Never overwrite live interactive state with a server response.**
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
31
|
+
│ THE ANTI-PATTERN (DON'T DO) │
|
|
32
|
+
│ │
|
|
33
|
+
│ 1. User drags annotation A → B │
|
|
34
|
+
│ 2. API save starts (position: B) │
|
|
35
|
+
│ 3. User continues dragging B → C │
|
|
36
|
+
│ 4. API returns with position: B │
|
|
37
|
+
│ 5. React Query cache updates → annotation snaps back to B ❌ │
|
|
38
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
39
|
+
|
|
40
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
41
|
+
│ LOCAL-FIRST PATTERN (DO THIS) │
|
|
42
|
+
│ │
|
|
43
|
+
│ 1. User drags annotation A → B │
|
|
44
|
+
│ 2. Visual offset tracks drag (local state only) │
|
|
45
|
+
│ 3. API save starts (position: B) │
|
|
46
|
+
│ 4. User continues dragging B → C │
|
|
47
|
+
│ 5. Drag flag blocks React Query sync │
|
|
48
|
+
│ 6. API ACK received → flag cleared, local C preserved ✅ │
|
|
49
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Architecture Layers
|
|
55
|
+
|
|
56
|
+
### Layer 1: Interaction Layer (Visual Delta)
|
|
57
|
+
|
|
58
|
+
Ephemeral state for **active pointer interactions only**. Lives in component-local `useState`.
|
|
59
|
+
|
|
60
|
+
```tsx
|
|
61
|
+
// In annotation-pin.tsx or annotation-box.tsx
|
|
62
|
+
const [dragOffset, setDragOffset] = useState<{ x: number; y: number } | null>(null);
|
|
63
|
+
|
|
64
|
+
// Render position = base position + visual delta
|
|
65
|
+
const effectiveX = dragOffset ? annotation.x + dragOffset.x : annotation.x;
|
|
66
|
+
const effectiveY = dragOffset ? annotation.y + dragOffset.y : annotation.y;
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
**Key Properties:**
|
|
70
|
+
- Cleared after commit (pointer-up), typically via `useEffect` when `annotation` base geometry updates from parent
|
|
71
|
+
- Never persisted or sent to server
|
|
72
|
+
- Provides 60fps smooth drag regardless of React re-renders
|
|
73
|
+
|
|
74
|
+
### Layer 2: Local Document Store (React State)
|
|
75
|
+
|
|
76
|
+
The **source of truth** for what user sees. Managed by `useAnnotationsWithHistory`.
|
|
77
|
+
|
|
78
|
+
```tsx
|
|
79
|
+
// In use-annotations-with-history.ts
|
|
80
|
+
const [annotations, setAnnotations] = useState<AttachmentAnnotation[]>(initialAnnotations);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Key Properties:**
|
|
84
|
+
- Updated on drag complete (pointer-up)
|
|
85
|
+
- Updated on undo/redo operations
|
|
86
|
+
- Synced from React Query ONLY when not dragging and no pending changes
|
|
87
|
+
- History entries reference this state for undo/redo
|
|
88
|
+
|
|
89
|
+
### Layer 3: React Query Cache (Server Shadow)
|
|
90
|
+
|
|
91
|
+
Shadow of server state for **data fetching, caching, and optimistic updates**.
|
|
92
|
+
|
|
93
|
+
```tsx
|
|
94
|
+
// In use-annotation-integration.ts
|
|
95
|
+
queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
|
|
96
|
+
old?.map(ann => ann.id === annotationId ? { ...ann, ...update } : ann)
|
|
97
|
+
);
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Key Properties:**
|
|
101
|
+
- Invalidated after successful mutation (but sync blocked during drag)
|
|
102
|
+
- Used for initial load and refetch after idle
|
|
103
|
+
- Optimistic updates prevent UI delay on mutations
|
|
104
|
+
|
|
105
|
+
### Layer 4: Server API (Canonical State)
|
|
106
|
+
|
|
107
|
+
Postgres database via Drizzle ORM. The **ultimate source of truth** for persistence.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Data Flow Diagram
|
|
112
|
+
|
|
113
|
+
```mermaid
|
|
114
|
+
graph TD
|
|
115
|
+
subgraph "Component Layer"
|
|
116
|
+
PIN[annotation-pin.tsx]
|
|
117
|
+
BOX[annotation-box.tsx]
|
|
118
|
+
VD[Visual Delta State<br/>dragOffset / visualDelta]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
subgraph "Hook Layer"
|
|
122
|
+
INT[useAnnotationIntegration]
|
|
123
|
+
HIST[useAnnotationsWithHistory]
|
|
124
|
+
TOOLS[useAnnotationTools]
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
subgraph "Cache Layer"
|
|
128
|
+
RQ[React Query Cache]
|
|
129
|
+
OPT[Optimistic Updates]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
subgraph "Server Layer"
|
|
133
|
+
API["/api/.../annotations"]
|
|
134
|
+
DB[(PostgreSQL)]
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
PIN --> VD
|
|
138
|
+
BOX --> VD
|
|
139
|
+
VD -->|"pointer up"| INT
|
|
140
|
+
INT --> HIST
|
|
141
|
+
INT --> RQ
|
|
142
|
+
RQ --> OPT
|
|
143
|
+
OPT --> API
|
|
144
|
+
API --> DB
|
|
145
|
+
|
|
146
|
+
HIST -->|"history"| TOOLS
|
|
147
|
+
TOOLS -->|"undo/redo"| HIST
|
|
148
|
+
|
|
149
|
+
RQ -.->|"sync blocked<br/>during drag"| HIST
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Implementation: Annotation Components
|
|
155
|
+
|
|
156
|
+
### Pin Annotation (`annotation-pin.tsx`)
|
|
157
|
+
|
|
158
|
+
```tsx
|
|
159
|
+
// Pattern: local visual delta + single commit on pointer-up
|
|
160
|
+
|
|
161
|
+
// 1. Local visual delta for smooth drag
|
|
162
|
+
const [dragOffset, setDragOffset] = useState<{ x: number; y: number } | null>(null);
|
|
163
|
+
|
|
164
|
+
// 2. Clear offset when parent state updates (prevents double-apply)
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
if (dragOffset !== null) {
|
|
167
|
+
setDragOffset(null);
|
|
168
|
+
}
|
|
169
|
+
}, [annotation.x, annotation.y]);
|
|
170
|
+
|
|
171
|
+
// 3. During drag: update visual delta only (no parent state changes)
|
|
172
|
+
const handlePointerMove = (event: PointerEvent) => {
|
|
173
|
+
// ... calculate position ...
|
|
174
|
+
setDragOffset({
|
|
175
|
+
x: clampedX - annotation.x,
|
|
176
|
+
y: clampedY - annotation.y,
|
|
177
|
+
});
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
// 4. On drag complete: commit final position to parent
|
|
181
|
+
const handlePointerUp = () => {
|
|
182
|
+
if (isDraggingRef.current && lastPositionRef.current) {
|
|
183
|
+
onMoveComplete?.(annotation.id, lastPositionRef.current);
|
|
184
|
+
}
|
|
185
|
+
// Note: Don't clear dragOffset here - let useEffect do it
|
|
186
|
+
onDragEnd?.(annotation.id);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// 5. Render using visual delta
|
|
190
|
+
style={{
|
|
191
|
+
left: dragOffset ? `${(annotation.x + dragOffset.x) * 100}%` : `${annotation.x * 100}%`,
|
|
192
|
+
top: dragOffset ? `${(annotation.y + dragOffset.y) * 100}%` : `${annotation.y * 100}%`,
|
|
193
|
+
}}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Box Annotation (`annotation-box.tsx`)
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
// Pattern: local visual delta for move/resize + single commit on pointer-up
|
|
200
|
+
|
|
201
|
+
// 1. Visual delta for both move and resize
|
|
202
|
+
const [visualDelta, setVisualDelta] = useState<{
|
|
203
|
+
startDx: number; startDy: number;
|
|
204
|
+
endDx: number; endDy: number
|
|
205
|
+
} | null>(null);
|
|
206
|
+
|
|
207
|
+
// 2. Calculate effective positions
|
|
208
|
+
const effectiveStart = visualDelta
|
|
209
|
+
? { x: annotation.start.x + visualDelta.startDx, y: annotation.start.y + visualDelta.startDy }
|
|
210
|
+
: annotation.start;
|
|
211
|
+
const effectiveEnd = visualDelta
|
|
212
|
+
? { x: annotation.end.x + visualDelta.endDx, y: annotation.end.y + visualDelta.endDy }
|
|
213
|
+
: annotation.end;
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## Implementation: Integration Hook
|
|
219
|
+
|
|
220
|
+
### Sync Guard Pattern (`use-annotation-integration.ts`)
|
|
221
|
+
|
|
222
|
+
```tsx
|
|
223
|
+
// ✅ RECOMMENDED PATTERN - Prevents sync during drag and ignores stale ACKs
|
|
224
|
+
|
|
225
|
+
// Track interaction + pending saves per annotation
|
|
226
|
+
const draggingIdsRef = useRef(new Set<string>());
|
|
227
|
+
const clientRevisionByIdRef = useRef(new Map<string, number>());
|
|
228
|
+
const pendingRevisionByIdRef = useRef(new Map<string, number>()); // id -> latest pending clientRevision
|
|
229
|
+
|
|
230
|
+
const isSyncBlocked = () =>
|
|
231
|
+
draggingIdsRef.current.size > 0 || pendingRevisionByIdRef.current.size > 0;
|
|
232
|
+
|
|
233
|
+
// Block sync during active interaction or pending saves
|
|
234
|
+
useEffect(() => {
|
|
235
|
+
if (isSyncBlocked()) return;
|
|
236
|
+
if (query.data && !query.isFetching) setAnnotations(query.data);
|
|
237
|
+
}, [query.data, query.isFetching, setAnnotations]);
|
|
238
|
+
|
|
239
|
+
// Commit a move on pointer-up (single local write + single save intent)
|
|
240
|
+
const handleAnnotationMoveComplete = useCallback(
|
|
241
|
+
(annotationId: string, position: { x: number; y: number }) => {
|
|
242
|
+
localHandleMove(annotationId, position);
|
|
243
|
+
const nextClientRevision = (clientRevisionByIdRef.current.get(annotationId) ?? 0) + 1;
|
|
244
|
+
clientRevisionByIdRef.current.set(annotationId, nextClientRevision);
|
|
245
|
+
pendingRevisionByIdRef.current.set(annotationId, nextClientRevision);
|
|
246
|
+
debouncedUpdate(annotationId, newShape, nextClientRevision);
|
|
247
|
+
},
|
|
248
|
+
[...]
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Clear pending only if this response matches the latest pending revision
|
|
252
|
+
onSettled: (_data, _error, variables) => {
|
|
253
|
+
const pending = pendingRevisionByIdRef.current.get(variables.annotationId);
|
|
254
|
+
if (pending === variables.clientRevision) {
|
|
255
|
+
pendingRevisionByIdRef.current.delete(variables.annotationId);
|
|
256
|
+
}
|
|
257
|
+
if (!isSyncBlocked()) void queryClient.invalidateQueries({ queryKey });
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// Expose drag state setter
|
|
261
|
+
setDragging: (annotationId: string, isDragging: boolean) => {
|
|
262
|
+
if (isDragging) draggingIdsRef.current.add(annotationId);
|
|
263
|
+
else draggingIdsRef.current.delete(annotationId);
|
|
264
|
+
},
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Debounced Save Strategy
|
|
268
|
+
|
|
269
|
+
```tsx
|
|
270
|
+
// Pattern: debounce commit-triggered saves (coalesce rapid edits)
|
|
271
|
+
|
|
272
|
+
const debouncedUpdate = useDebouncedCallback(
|
|
273
|
+
(annotationId: string, shape: AnnotationShape, clientRevision: number) => {
|
|
274
|
+
void updateMutation.mutateAsync({ annotationId, shape, clientRevision });
|
|
275
|
+
},
|
|
276
|
+
500 // Debounce interval
|
|
277
|
+
);
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
**Why 500ms?**
|
|
281
|
+
- Short enough to feel "autosave"
|
|
282
|
+
- Long enough to coalesce rapid position changes
|
|
283
|
+
- Reduces API calls during continuous drag
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Server API Contract
|
|
288
|
+
|
|
289
|
+
### Ordering & Stale ACK Protection (Recommended)
|
|
290
|
+
|
|
291
|
+
Debounce reduces call volume, but it does not prevent **out-of-order responses**. The client must be able to detect and ignore stale ACKs so a late response can’t “confirm” an older position and trigger a snap-back via cache sync.
|
|
292
|
+
|
|
293
|
+
**Client-side minimum (works even if the server is ACK-only):**
|
|
294
|
+
- Maintain a monotonically increasing `clientRevision` **per annotation**, incremented on each local commit (pointer-up / resize end).
|
|
295
|
+
- Attach `clientRevision` to the PATCH request.
|
|
296
|
+
- When a response returns, treat it as **ACK only** and ignore it unless it matches the latest pending `clientRevision` for that annotation.
|
|
297
|
+
|
|
298
|
+
**Server-side recommended (safer for multi-tab / future collaboration):**
|
|
299
|
+
- Store `revision` on the annotation row.
|
|
300
|
+
- PATCH body includes `{ expectedRevision, clientRevision, shape }`.
|
|
301
|
+
- Server updates with optimistic concurrency and returns `{ revision: newRevision }` (or `409 Conflict` if `expectedRevision` is stale).
|
|
302
|
+
|
|
303
|
+
### Mutation Flow (ACK-Only)
|
|
304
|
+
|
|
305
|
+
```mermaid
|
|
306
|
+
sequenceDiagram
|
|
307
|
+
participant U as User
|
|
308
|
+
participant C as Client
|
|
309
|
+
participant S as Server
|
|
310
|
+
|
|
311
|
+
U->>C: Drag annotation A→B
|
|
312
|
+
C->>C: Update visual delta (local)
|
|
313
|
+
U->>C: Release pointer
|
|
314
|
+
C->>C: Commit B to local state
|
|
315
|
+
C->>C: clientRevision=r1, mark pending
|
|
316
|
+
C->>S: PATCH /api/.../annotations/:id (clientRevision=r1)
|
|
317
|
+
Note over C: isSaving = true
|
|
318
|
+
U->>C: Move again (B→C), release
|
|
319
|
+
C->>C: Commit C, clientRevision=r2
|
|
320
|
+
C->>S: PATCH /api/.../annotations/:id (clientRevision=r2)
|
|
321
|
+
S-->>C: 200 OK (ACK r1)
|
|
322
|
+
C->>C: Ignore stale ACK (r1 < r2)
|
|
323
|
+
S-->>C: 200 OK (ACK r2)
|
|
324
|
+
C->>C: Clear pending for r2
|
|
325
|
+
C->>C: Invalidate query (safe now, no pending)
|
|
326
|
+
Note over C: isSaving = false
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Optimistic Update Pattern
|
|
330
|
+
|
|
331
|
+
```tsx
|
|
332
|
+
// In updateMutation
|
|
333
|
+
onMutate: async ({ annotationId, shape }) => {
|
|
334
|
+
// 1. Cancel outgoing refetches
|
|
335
|
+
await queryClient.cancelQueries({ queryKey });
|
|
336
|
+
|
|
337
|
+
// 2. Snapshot previous value for hard-error rollback (permission/quota/validation)
|
|
338
|
+
const previousAnnotations = queryClient.getQueryData<AttachmentAnnotation[]>(queryKey);
|
|
339
|
+
|
|
340
|
+
// 3. Optimistically update cache
|
|
341
|
+
if (shape) {
|
|
342
|
+
queryClient.setQueryData<AttachmentAnnotation[]>(queryKey, (old) =>
|
|
343
|
+
old?.map((ann) => (ann.id === annotationId ? { ...ann, shape } : ann))
|
|
344
|
+
);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return { previousAnnotations };
|
|
348
|
+
},
|
|
349
|
+
|
|
350
|
+
onError: (error, variables, context) => {
|
|
351
|
+
/**
|
|
352
|
+
* Recommended UX for a local-first editor:
|
|
353
|
+
* - For transient errors (network/5xx/timeouts): keep local state, mark "unsaved", and retry/backoff.
|
|
354
|
+
* - Only rollback for hard errors where the change cannot be accepted (403/404/quota/validation),
|
|
355
|
+
* then exit edit mode or show an upgrade/permission callout.
|
|
356
|
+
*/
|
|
357
|
+
markAnnotationUnsaved(variables.annotationId, { error });
|
|
358
|
+
if (isHardSaveError(error) && context?.previousAnnotations) {
|
|
359
|
+
queryClient.setQueryData(queryKey, context.previousAnnotations);
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
|
|
363
|
+
onSettled: (_data, _error, variables) => {
|
|
364
|
+
const pending = pendingRevisionByIdRef.current.get(variables.annotationId);
|
|
365
|
+
if (pending === variables.clientRevision) {
|
|
366
|
+
pendingRevisionByIdRef.current.delete(variables.annotationId);
|
|
367
|
+
}
|
|
368
|
+
if (pendingRevisionByIdRef.current.size === 0 && draggingIdsRef.current.size === 0) {
|
|
369
|
+
void queryClient.invalidateQueries({ queryKey });
|
|
370
|
+
}
|
|
371
|
+
},
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Helper Functions
|
|
375
|
+
|
|
376
|
+
```tsx
|
|
377
|
+
// Determine if error is a hard (non-retryable) error
|
|
378
|
+
const HARD_ERROR_CODES = [403, 404, 402, 422];
|
|
379
|
+
|
|
380
|
+
function isHardSaveError(error: unknown): boolean {
|
|
381
|
+
if (error instanceof Error && 'status' in error) {
|
|
382
|
+
return HARD_ERROR_CODES.includes((error as any).status);
|
|
383
|
+
}
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Track unsaved annotations for UI indication
|
|
388
|
+
const unsavedAnnotationsRef = useRef(new Map<string, { error?: Error; retryCount: number }>());
|
|
389
|
+
|
|
390
|
+
function markAnnotationUnsaved(annotationId: string, opts?: { error?: Error }) {
|
|
391
|
+
const current = unsavedAnnotationsRef.current.get(annotationId);
|
|
392
|
+
unsavedAnnotationsRef.current.set(annotationId, {
|
|
393
|
+
error: opts?.error,
|
|
394
|
+
retryCount: (current?.retryCount ?? 0) + 1,
|
|
395
|
+
});
|
|
396
|
+
// Optionally trigger re-render or toast
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function clearAnnotationUnsaved(annotationId: string) {
|
|
400
|
+
unsavedAnnotationsRef.current.delete(annotationId);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Check if any annotations have unsaved changes
|
|
404
|
+
function hasUnsavedChanges(): boolean {
|
|
405
|
+
return unsavedAnnotationsRef.current.size > 0;
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
### Flush on Tab Close (Recommended)
|
|
410
|
+
|
|
411
|
+
```tsx
|
|
412
|
+
// Ensure pending saves are flushed when user leaves
|
|
413
|
+
useEffect(() => {
|
|
414
|
+
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
415
|
+
if (pendingRevisionByIdRef.current.size > 0) {
|
|
416
|
+
// Flush any debounced saves immediately
|
|
417
|
+
debouncedUpdate.flush?.();
|
|
418
|
+
// Show browser warning
|
|
419
|
+
e.preventDefault();
|
|
420
|
+
e.returnValue = 'You have unsaved changes.';
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
|
|
424
|
+
window.addEventListener('beforeunload', handleBeforeUnload);
|
|
425
|
+
return () => window.removeEventListener('beforeunload', handleBeforeUnload);
|
|
426
|
+
}, []);
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Undo + Save Interaction
|
|
430
|
+
|
|
431
|
+
When a user undoes while a save is pending, the system should:
|
|
432
|
+
|
|
433
|
+
1. **Apply undo immediately** to local state (history is the source of truth for undo/redo)
|
|
434
|
+
2. **Cancel or supersede the pending save** — the pending save's `clientRevision` will become stale
|
|
435
|
+
3. **Queue a new save** for the undone position with a new `clientRevision`
|
|
436
|
+
|
|
437
|
+
```tsx
|
|
438
|
+
// In undo handler
|
|
439
|
+
const handleUndo = useCallback((entry: AnnotationHistoryEntry) => {
|
|
440
|
+
// Apply undo to local state
|
|
441
|
+
applyUndo(entry);
|
|
442
|
+
|
|
443
|
+
// The pending save (if any) will be ignored when it returns because
|
|
444
|
+
// clientRevision won't match the latest pendingRevisionByIdRef entry.
|
|
445
|
+
// Queue a new save for the undone position:
|
|
446
|
+
const restoredShape = entry.previousSnapshot?.shape;
|
|
447
|
+
if (restoredShape) {
|
|
448
|
+
const nextClientRevision = (clientRevisionByIdRef.current.get(entry.annotationId) ?? 0) + 1;
|
|
449
|
+
clientRevisionByIdRef.current.set(entry.annotationId, nextClientRevision);
|
|
450
|
+
pendingRevisionByIdRef.current.set(entry.annotationId, nextClientRevision);
|
|
451
|
+
debouncedUpdate(entry.annotationId, restoredShape, nextClientRevision);
|
|
452
|
+
}
|
|
453
|
+
}, [applyUndo, debouncedUpdate]);
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Auth (RBAC) & Plan Limits (Product-Aligned)
|
|
457
|
+
|
|
458
|
+
UI SyncUp’s product model has role-gated creation/editing and plan-based limits. The autosave contract should explicitly document these outcomes so the UI stays local-first without silently failing:
|
|
459
|
+
- `403 Forbidden`: user lacks permission to edit annotations → keep UI stable, exit edit mode, show a permission callout.
|
|
460
|
+
- Quota/plan/validation errors (`402/409/422` depending on API semantics) → keep UI stable, mark unsaved, show upgrade/limit messaging, and stop retrying until user action resolves it.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
464
|
+
## State Machine
|
|
465
|
+
|
|
466
|
+
```
|
|
467
|
+
┌─────────────┐
|
|
468
|
+
│ IDLE │ ◄── Initial state
|
|
469
|
+
│ │ annotations synced with server
|
|
470
|
+
└──────┬──────┘
|
|
471
|
+
│
|
|
472
|
+
│ onPointerDown (drag start)
|
|
473
|
+
▼
|
|
474
|
+
┌─────────────┐
|
|
475
|
+
│ DRAGGING │ ◄── draggingIdsRef contains id
|
|
476
|
+
│ │ Visual delta active
|
|
477
|
+
└──────┬──────┘ React Query sync BLOCKED
|
|
478
|
+
│
|
|
479
|
+
│ onPointerUp (drag end)
|
|
480
|
+
▼
|
|
481
|
+
┌─────────────┐
|
|
482
|
+
│ PENDING │ ◄── pendingRevisionByIdRef has entry
|
|
483
|
+
│ │ Save scheduled for latest clientRevision
|
|
484
|
+
└──────┬──────┘ React Query sync BLOCKED
|
|
485
|
+
│
|
|
486
|
+
│ debounce fires (500ms)
|
|
487
|
+
▼
|
|
488
|
+
┌─────────────┐
|
|
489
|
+
│ SAVING │ ◄── updateMutation.isPending = true
|
|
490
|
+
│ │ API call in flight (may overlap older requests)
|
|
491
|
+
└──────┬──────┘ React Query sync BLOCKED
|
|
492
|
+
│
|
|
493
|
+
┌────────────┴────────────┐
|
|
494
|
+
│ │
|
|
495
|
+
onSuccess onError
|
|
496
|
+
│ │
|
|
497
|
+
▼ ▼
|
|
498
|
+
┌─────────────────┐ ┌──────────────────┐
|
|
499
|
+
│ SUCCESS │ │ ERROR │
|
|
500
|
+
│ ACK received │ │ Mark unsaved │
|
|
501
|
+
└────────┬────────┘ └────────┬─────────┘
|
|
502
|
+
│ │
|
|
503
|
+
│ Clear pending flag │
|
|
504
|
+
│ Invalidate query │
|
|
505
|
+
└────────────┬────────────┘
|
|
506
|
+
│
|
|
507
|
+
▼
|
|
508
|
+
┌─────────────┐
|
|
509
|
+
│ IDLE │ ◄── React Query sync ALLOWED
|
|
510
|
+
└─────────────┘
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Note:** This state machine is effectively **per-annotation** (or per active interaction). Sync-blocking can be implemented at the document level (“any dragging/pending blocks full sync”) or at the entity level (“only block applying server updates to interacting annotation IDs”).
|
|
514
|
+
|
|
515
|
+
---
|
|
516
|
+
|
|
517
|
+
## Testing Checklist
|
|
518
|
+
|
|
519
|
+
### Drag Stability Tests
|
|
520
|
+
|
|
521
|
+
| Test | Expected Behavior |
|
|
522
|
+
|------|-------------------|
|
|
523
|
+
| Drag A→B while save in-flight, continue to C | No snap back, ends at C |
|
|
524
|
+
| Rapid drag with multiple saves queued | Final position correct |
|
|
525
|
+
| Out-of-order ACKs (r1 returns after r2) | No snap back, stale ACK ignored |
|
|
526
|
+
| Network error mid-edit | UI stable, annotation marked unsaved, retry/backoff |
|
|
527
|
+
| Undo during save | Correct undo applied after save completes |
|
|
528
|
+
| 403 / quota response | UI stable, edit disabled / messaging shown |
|
|
529
|
+
|
|
530
|
+
### Manual Verification Steps
|
|
531
|
+
|
|
532
|
+
1. **Open** Issue Details with an attachment that has annotations
|
|
533
|
+
2. **Enable** Edit Mode (press `E` or click toggle)
|
|
534
|
+
3. **Drag** an annotation pin slowly, observe smooth movement
|
|
535
|
+
4. **Drag** quickly in circles, release — verify no snap-back
|
|
536
|
+
5. **Drag** while watching Network tab — confirm single save after debounce
|
|
537
|
+
6. **Simulate** slow network (DevTools: Slow 3G) and drag — confirm no flicker
|
|
538
|
+
7. **Undo** (Cmd+Z) immediately after drag — verify correct restoration
|
|
539
|
+
|
|
540
|
+
### Automated Tests
|
|
541
|
+
|
|
542
|
+
```typescript
|
|
543
|
+
// Property: Drag should never cause position to go backwards
|
|
544
|
+
describe('annotation-drag-stability', () => {
|
|
545
|
+
it('should not snap back during save', async () => {
|
|
546
|
+
// Setup: render annotation at position {x: 0.2, y: 0.2}
|
|
547
|
+
// Action: simulate drag to {x: 0.5, y: 0.5}
|
|
548
|
+
// Action: while save is pending, check position
|
|
549
|
+
// Assert: position >= {x: 0.5, y: 0.5}
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('should handle rapid position changes', async () => {
|
|
553
|
+
// Setup: render annotation
|
|
554
|
+
// Action: rapidly simulate 10 drag positions
|
|
555
|
+
// Assert: final position matches last drag position
|
|
556
|
+
// Assert: no intermediate positions visible
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
---
|
|
562
|
+
|
|
563
|
+
## Directory Structure
|
|
564
|
+
|
|
565
|
+
Following [structure.md](../../../../.ai/steering/structure.md):
|
|
566
|
+
|
|
567
|
+
```
|
|
568
|
+
src/features/annotations/
|
|
569
|
+
├── api/
|
|
570
|
+
│ └── annotations-api.ts # API fetchers with Zod schemas
|
|
571
|
+
├── components/
|
|
572
|
+
│ ├── annotation-pin.tsx # Pin with visual delta
|
|
573
|
+
│ ├── annotation-box.tsx # Box with visual delta
|
|
574
|
+
│ └── annotation-layer.tsx # Overlay renderer
|
|
575
|
+
├── hooks/
|
|
576
|
+
│ ├── use-annotation-integration.ts # Core hook with sync guard
|
|
577
|
+
│ ├── use-annotations-with-history.ts # Local state + undo/redo
|
|
578
|
+
│ └── use-annotation-tools.ts # Tool state + keyboard
|
|
579
|
+
├── types/
|
|
580
|
+
│ └── index.ts # Domain types
|
|
581
|
+
├── utils/
|
|
582
|
+
│ └── history-manager.ts # History entry creation
|
|
583
|
+
└── docs/
|
|
584
|
+
└── local_first_canvas_autosave_architecture.md # This document
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
---
|
|
588
|
+
|
|
589
|
+
## Performance Optimizations
|
|
590
|
+
|
|
591
|
+
### Current Optimizations ✅
|
|
592
|
+
|
|
593
|
+
| Optimization | Implementation |
|
|
594
|
+
|--------------|----------------|
|
|
595
|
+
| Visual delta in local state | `useState` in component, not parent |
|
|
596
|
+
| Debounced API calls | 500ms debounce in `useDebouncedCallback` |
|
|
597
|
+
| Sync guard during drag | `draggingIdsRef` + `pendingRevisionByIdRef` |
|
|
598
|
+
| Optimistic updates | React Query `onMutate` |
|
|
599
|
+
| Memoized callbacks | `useCallback` for all handlers |
|
|
600
|
+
| History deduplication | `lastProcessedHistoryId` check |
|
|
601
|
+
|
|
602
|
+
### Future Optimizations
|
|
603
|
+
|
|
604
|
+
| Optimization | Priority | Description |
|
|
605
|
+
|--------------|----------|-------------|
|
|
606
|
+
| Op coalescing | Medium | Replace multiple MOVE ops with single final position |
|
|
607
|
+
| RAF-based visual updates | Low | Use `requestAnimationFrame` for even smoother drag |
|
|
608
|
+
| IndexedDB op log | Low | Persist pending ops for crash recovery |
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## Developer Guidelines
|
|
613
|
+
|
|
614
|
+
### DO ✅
|
|
615
|
+
|
|
616
|
+
- Use visual delta (`dragOffset`, `visualDelta`) for active pointer interactions
|
|
617
|
+
- Check `isSyncBlocked()` (`draggingIdsRef` + `pendingRevisionByIdRef`) before syncing from React Query
|
|
618
|
+
- Clear pending per annotation in `onSettled` only when `clientRevision` matches the latest pending revision
|
|
619
|
+
- Let `useEffect` clear visual delta when parent position updates
|
|
620
|
+
- Use `onMoveComplete` for final state commit (not `onMove` during drag)
|
|
621
|
+
|
|
622
|
+
### DON'T ❌
|
|
623
|
+
|
|
624
|
+
- Never apply server response directly to local state during drag
|
|
625
|
+
- Don't clear visual delta manually in `handlePointerUp`
|
|
626
|
+
- Don't call `setAnnotations` inside `handlePointerMove`
|
|
627
|
+
- Don't invalidate React Query during active drag
|
|
628
|
+
- Don't create history entries during drag (only on complete)
|
|
629
|
+
|
|
630
|
+
---
|
|
631
|
+
|
|
632
|
+
## Migration from Old Pattern
|
|
633
|
+
|
|
634
|
+
If you encounter code that directly updates parent state during drag:
|
|
635
|
+
|
|
636
|
+
```tsx
|
|
637
|
+
// ❌ OLD PATTERN - causes flicker
|
|
638
|
+
const handlePointerMove = (event) => {
|
|
639
|
+
const newPosition = calculatePosition(event);
|
|
640
|
+
onMove(annotation.id, newPosition); // Updates parent state during drag
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
// ✅ NEW PATTERN - smooth drag
|
|
644
|
+
const handlePointerMove = (event) => {
|
|
645
|
+
const newPosition = calculatePosition(event);
|
|
646
|
+
setVisualDelta({
|
|
647
|
+
x: newPosition.x - annotation.x,
|
|
648
|
+
y: newPosition.y - annotation.y,
|
|
649
|
+
});
|
|
650
|
+
};
|
|
651
|
+
|
|
652
|
+
const handlePointerUp = () => {
|
|
653
|
+
if (lastPositionRef.current) {
|
|
654
|
+
onMoveComplete(annotation.id, lastPositionRef.current); // Single commit
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
---
|
|
660
|
+
|
|
661
|
+
## References
|
|
662
|
+
|
|
663
|
+
- [ANNOTATIONS_ARCHITECTURE.md](./ANNOTATIONS_ARCHITECTURE.md) — Feature overview
|
|
664
|
+
- [ANNOTATION_SAVE_ARCHITECTURE.md](./ANNOTATION_SAVE_ARCHITECTURE.md) — Save flow diagrams
|
|
665
|
+
- [BOX_ANNOTATION_GUIDE.md](./BOX_ANNOTATION_GUIDE.md) — Box drag/resize implementation
|
|
666
|
+
- [STALE_CLOSURE_FIX.md](./STALE_CLOSURE_FIX.md) — React closure pitfalls
|
|
667
|
+
- [structure.md](../../../../.ai/steering/structure.md) — Project structure
|
|
668
|
+
- [tech.md](../../../../.ai/steering/tech.md) — Tech stack
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
**Document Status**: ✅ Adapted for UI SyncUp
|
|
673
|
+
**Last Updated**: 2024-12-17
|
|
674
|
+
**Follows**: Feature-first architecture per [structure.md](../../../../.ai/steering/structure.md)
|