Skip to content

feat(deployment beta): projects UI for Unkey Deploy#3662

Merged
chronark merged 20 commits intomainfrom
ENG-1902-projects-ui
Aug 1, 2025
Merged

feat(deployment beta): projects UI for Unkey Deploy#3662
chronark merged 20 commits intomainfrom
ENG-1902-projects-ui

Conversation

@mcstepp
Copy link
Collaborator

@mcstepp mcstepp commented Jul 25, 2025

What does this PR do?

Fixes #3518 , #3522

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • No tests, this is a WIP for demo purposes
  • make sure to opt into "deployments" beta feature

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • [] If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Added a new interactive API diff viewer with filtering, grouping, and multiple visualization modes for deployment comparisons.
    • Introduced project detail pages with tabs for overview, deployments, and settings.
    • Implemented deployment selection and comparison pages with detailed diff results.
    • Added a client-side projects dashboard component supporting project creation, searching, and listing.
    • Enhanced workspace sidebar navigation with a new "Projects" section (Beta).
    • Introduced deployment detail pages with feature opt-in prompts.
  • Bug Fixes

    • Improved user feedback for missing or invalid project IDs and feature access restrictions.
  • Refactor

    • Replaced version-centric terminology and data structures with deployment-centric models across backend, database, and API layers.
    • Removed legacy branch and version management, including related UI components and database queries.
    • Updated control plane and workflow logic to operate on deployments rather than versions.
  • Chores

    • Added comprehensive contributor documentation in CLAUDE.md.
    • Updated SQL queries, generated code, and tRPC procedures to support deployments.
    • Refined Docker development environment setup scripts without functional changes.
  • Revert

    • Removed all version and branch related files, routes, and procedures from backend and database code.

@linear
Copy link

linear bot commented Jul 25, 2025

ENG-1902 Projects UI

@changeset-bot
Copy link

changeset-bot bot commented Jul 25, 2025

⚠️ No Changeset found

Latest commit: 0412787

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jul 25, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
dashboard ⬜️ Ignored (Inspect) Visit Preview Aug 1, 2025 4:36pm
engineering ⬜️ Ignored (Inspect) Visit Preview Aug 1, 2025 4:36pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 25, 2025

📝 Walkthrough

Walkthrough

This change introduces a new Projects UI for managing deployment projects. It adds server-side and client-side React components for listing, creating, and viewing projects and their details, including deployments. The backend is refactored to use "deployments" instead of "versions," updating database schemas, queries, and service logic accordingly. Several legacy version/branch-related files and queries are removed.

Changes

Cohort / File(s) Change Summary
Projects UI: Server and Client Components
apps/dashboard/app/(app)/projects/page.tsx, apps/dashboard/app/(app)/projects/projects-client.tsx, apps/dashboard/app/(app)/projects/[projectId]/page.tsx, apps/dashboard/app/(app)/projects/[projectId]/deployments/[deploymentId]/page.tsx
Added/rewrote server- and client-side React components for listing, creating, and detailing projects and deployments. Handles beta feature gating, authentication, and UI state.
Deployments Diff and Comparison UI
apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx, apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx, apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx, apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/constants.ts, apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/types.ts
Added new pages and components for comparing deployments, viewing OpenAPI diffs, and displaying diff data with filtering and visualization. Introduced supporting types and sample data.
Sidebar Navigation
apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx
Added "Projects" navigation item, gated by the deployments beta feature.
TRPC Routers: Deployments
apps/dashboard/lib/trpc/routers/deployment/index.ts, apps/dashboard/lib/trpc/routers/deployment/list.ts, apps/dashboard/lib/trpc/routers/deployment/listByEnvironment.ts, apps/dashboard/lib/trpc/routers/deployment/listByProject.ts, apps/dashboard/lib/trpc/routers/deployment/getById.ts, apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, apps/dashboard/lib/trpc/routers/index.ts, apps/dashboard/lib/trpc/routers/project/index.ts, apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts
Added deployment router and procedures for listing, querying, and diffing deployments. Updated project router to remove branch support. Extended beta feature schema and opt-in logic for deployments.
Database Schema and Queries: Deployments Migration
internal/db/src/schema/workspaces.ts, go/pkg/db/models_generated.go, go/pkg/db/querier_generated.go, go/pkg/db/querier_bulk_generated.go, go/pkg/db/build_find_by_id.sql_generated.go, go/pkg/db/build_find_latest_by_deployment_id.sql_generated.go, go/pkg/db/build_insert.sql_generated.go, go/pkg/db/bulk_build_insert.sql.go, go/pkg/db/bulk_deployment_insert.sql.go, go/pkg/db/bulk_deployment_step_insert.sql.go, go/pkg/db/bulk_route_insert.sql.go, go/pkg/db/deployment_find_by_id.sql_generated.go, go/pkg/db/deployment_insert.sql_generated.go, go/pkg/db/deployment_step_find_by_deployment_id.sql_generated.go, go/pkg/db/deployment_step_insert.sql_generated.go, go/pkg/db/deployment_update_openapi_spec.sql_generated.go, go/pkg/db/deployment_update_status.sql_generated.go, go/pkg/db/queries/build_find_by_id.sql, go/pkg/db/queries/build_find_latest_by_deployment_id.sql, go/pkg/db/queries/build_insert.sql, go/pkg/db/queries/deployment_find_by_id.sql, go/pkg/db/queries/deployment_insert.sql, go/pkg/db/queries/deployment_step_find_by_deployment_id.sql, go/pkg/db/queries/deployment_step_insert.sql, go/pkg/db/queries/deployment_update_openapi_spec.sql, go/pkg/db/queries/deployment_update_status.sql, go/pkg/db/queries/route_find_by_deployment_id.sql, go/pkg/db/queries/route_insert.sql
Migrated all models, queries, and SQL from "version" and "branch" to "deployment". Added new deployment-related queries and update operations. Updated schema types and relationships.
Go Services: Deployment Refactor
go/apps/ctrl/run.go, go/apps/ctrl/services/deployment/create_version.go, go/apps/ctrl/services/deployment/deploy_workflow.go, go/apps/ctrl/services/deployment/get_version.go, go/apps/ctrl/services/deployment/service.go, go/apps/ctrl/services/openapi/utils.go, go/cmd/deploy/control_plane.go, go/cmd/deploy/main.go
Refactored all Go service logic, workflow, and CLI from "version" to "deployment" terminology and logic. Updated all struct fields, method names, and workflow steps accordingly.
Removed Legacy Version/Branch Files
apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx, apps/dashboard/app/(app)/versions/page.tsx, apps/dashboard/lib/trpc/routers/version/index.ts, apps/dashboard/lib/trpc/routers/version/list.ts, go/pkg/db/branch_find_by_project_name.sql_generated.go, go/pkg/db/build_step_insert.sql_generated.go, go/pkg/db/bulk_branch_insert.sql.go, go/pkg/db/bulk_branch_upsert.sql.go, go/pkg/db/queries/branch_find_by_project_name.sql, go/pkg/db/queries/branch_insert.sql, go/pkg/db/queries/branch_upsert.sql, go/pkg/db/queries/build_step_find_by_build_id.sql, go/pkg/db/queries/version_update_openapi_spec.sql
Deleted all files and queries related to branches and versions, removing legacy code now superseded by deployments.
Go Demo API: OpenAPI Spec Update
go/demo_api/main.go
Replaced the demo API's OpenAPI spec and endpoints with a new version, introducing breaking changes, new endpoints, and stricter validation.
Dockerfile and Documentation
go/deploy/Dockerfile.dev, CLAUDE.md
Updated Dockerfile scripting for environment and entrypoint setup. Added new contributor documentation for Claude.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant DashboardUI
    participant TRPC Router
    participant Database

    User->>DashboardUI: Visit /projects
    DashboardUI->>TRPC Router: Fetch projects (if authorized and beta enabled)
    TRPC Router->>Database: Query projects for workspace/org
    Database-->>TRPC Router: Return project list
    TRPC Router-->>DashboardUI: Project list data
    DashboardUI-->>User: Display projects, allow creation

    User->>DashboardUI: Select a project (/projects/[projectId])
    DashboardUI->>TRPC Router: Fetch project details and deployments
    TRPC Router->>Database: Query project and deployments
    Database-->>TRPC Router: Return project/deployment data
    TRPC Router-->>DashboardUI: Project/deployment details
    DashboardUI-->>User: Show project overview, deployments, actions

    User->>DashboardUI: Compare deployments (/projects/[projectId]/diff)
    DashboardUI->>TRPC Router: Fetch deployments for selection
    User->>DashboardUI: Select two deployments
    DashboardUI->>TRPC Router: Fetch OpenAPI diff (getOpenApiDiff)
    TRPC Router->>Database: Retrieve OpenAPI specs for deployments
    TRPC Router->>External API: (Optional) Fetch diff from control plane
    TRPC Router-->>DashboardUI: Return diff data
    DashboardUI-->>User: Show diff viewer with filtering and visualization
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~90+ minutes

Assessment against linked issues

Objective Addressed Explanation
Projects UI at /projects: list, create, and view projects (#3518)
Project detail page at /projects/[projectID]: overview, deployments, and related info (#3518)
Ability to see deployed versions (now deployments) and related details (#3518)
Branches listing and visibility in project details (#3518) Branches-related UI and backend logic are removed; only deployments are shown.
Rollback and advanced version management (future consideration) (#3518) UI and backend are structured to allow future rollback, but no rollback action is implemented in this PR.

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Major refactor of Go backend from "version" to "deployment" terminology and logic (e.g., go/pkg/db/models_generated.go, go/apps/ctrl/services/deployment/*, etc.) The linked issue focuses on the Projects UI and does not mention a backend-wide migration from versions to deployments. While this may be necessary for the new UI, the scope is broader than the UI objectives.
Replacement of demo API OpenAPI spec and endpoints (go/demo_api/main.go) Updating the demo API is unrelated to the Projects UI and not referenced in the issue objectives.
Addition of CLAUDE.md documentation Contributor documentation is not part of the UI objectives.
Dockerfile.dev scripting changes (go/deploy/Dockerfile.dev) Scripting changes for Docker development environment setup are unrelated to the Projects UI.

Suggested reviewers

  • mcstepp
  • perkinsjr
  • imeyer

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ENG-1902-projects-ui

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions
Copy link
Contributor

github-actions bot commented Jul 25, 2025

Thank you for following the naming conventions for pull request titles! 🙏

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 40

🔭 Outside diff range comments (5)
apps/dashboard/app/(app)/projects/page.tsx (1)

30-371: Component is too large - consider splitting into smaller components

The ProjectsPage component is over 370 lines long, which makes it harder to maintain and test. Consider extracting the following into separate components:

  • Project creation form (lines 140-216)
  • Project card (lines 245-332)
  • Empty state (lines 335-367)
  • Search and filter section (lines 100-117)

Would you like me to help refactor this component into smaller, more manageable pieces?

go/demo_api/main.go (4)

45-528: Move OpenAPI specification to a separate file

The OpenAPI specification is over 470 lines embedded in the code. This should be moved to a separate openapi.yaml file and loaded at runtime for better maintainability.

-  mux.HandleFunc("/openapi.yaml", func(w http.ResponseWriter, r *http.Request) {
-    spec := `openapi: 3.0.3
-info:
-  title: Demo API
-...
-        - timestamp`
-
-    w.Header().Set("Content-Type", "application/yaml")
-    w.WriteHeader(http.StatusOK)
-    fmt.Fprint(w, spec)
-  })
+  mux.HandleFunc("/openapi.yaml", func(w http.ResponseWriter, r *http.Request) {
+    spec, err := os.ReadFile("openapi.yaml")
+    if err != nil {
+      http.Error(w, "Failed to load OpenAPI spec", http.StatusInternalServerError)
+      return
+    }
+    w.Header().Set("Content-Type", "application/yaml")
+    w.WriteHeader(http.StatusOK)
+    w.Write(spec)
+  })

26-30: API version mismatch between handlers and OpenAPI spec

The handlers are still using /v1 paths while the OpenAPI spec declares the server URL as /v2. This inconsistency will cause the API to not match its documentation.

-  mux.HandleFunc("/v1/liveness", func(w http.ResponseWriter, r *http.Request) {
+  mux.HandleFunc("/v2/health", func(w http.ResponseWriter, r *http.Request) {
+    response := struct {
+      Status    string    `json:"status"`
+      Timestamp time.Time `json:"timestamp"`
+    }{
+      Status:    "healthy",
+      Timestamp: time.Now().UTC(),
+    }
     w.Header().Set("Content-Type", "application/json")
     w.WriteHeader(http.StatusOK)
-    fmt.Fprint(w, "OK")
+    if err := json.NewEncoder(w).Encode(response); err != nil {
+      log.Printf("Failed to encode response: %v", err)
+      http.Error(w, "Internal server error", http.StatusInternalServerError)
+    }
   })

Also applies to: 55-56


532-540: Add graceful shutdown handling

The server doesn't handle graceful shutdown, which could lead to dropped connections during deployments.

+  ctx, cancel := context.WithCancel(context.Background())
+  defer cancel()
+
+  sigChan := make(chan os.Signal, 1)
+  signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
+
+  go func() {
+    <-sigChan
+    log.Println("Shutting down server...")
+    shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 30*time.Second)
+    defer shutdownCancel()
+    if err := server.Shutdown(shutdownCtx); err != nil {
+      log.Printf("Server shutdown error: %v", err)
+    }
+  }()
+
   if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
     log.Fatal("Failed to start server:", err)
   }
+  log.Println("Server stopped")

17-42: Consider adding middleware for production readiness

The API lacks essential middleware for production use:

  • CORS headers for cross-origin requests
  • Request logging
  • Panic recovery
  • Request ID generation
  • Rate limiting

Consider using a middleware library like chi or gorilla/mux that provides these features out of the box, or implement custom middleware functions.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8eab750 and 01f9580.

📒 Files selected for processing (28)
  • apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/constants.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/types.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/versions/page.tsx (2 hunks)
  • apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (2 hunks)
  • apps/dashboard/lib/trpc/routers/branch/getByName.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/branch/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/branch/listByProject.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/getById.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/listByBranch.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/project/branches.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/version/index.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/version/list.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts (2 hunks)
  • go/apps/api/openapi/openapi-generated.yaml (1 hunks)
  • go/demo_api/main.go (1 hunks)
  • go/deploy/Dockerfile.dev (3 hunks)
  • internal/db/src/schema/workspaces.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/dashboard/lib/trpc/routers/version/index.ts
  • apps/dashboard/lib/trpc/routers/version/list.ts
🧰 Additional context used
🧠 Learnings (13)
apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (3)

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.

internal/db/src/schema/workspaces.ts (2)

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.

Learnt from: ogzhanolguncu
PR: #2707
File: apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts:63-63
Timestamp: 2024-12-05T13:27:55.555Z
Learning: In apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts, when determining the maximum number of rate limit overrides (max), the intentional use of const max = hasWorkspaceAccess("ratelimitOverrides", namespace.workspace) || 5; allows max to fall back to 5 when hasWorkspaceAccess returns 0 or false. This fallback behavior is expected and intended in the codebase.

apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts (4)

Learnt from: ogzhanolguncu
PR: #2707
File: apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts:63-63
Timestamp: 2024-12-05T13:27:55.555Z
Learning: In apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts, when determining the maximum number of rate limit overrides (max), the intentional use of const max = hasWorkspaceAccess("ratelimitOverrides", namespace.workspace) || 5; allows max to fall back to 5 when hasWorkspaceAccess returns 0 or false. This fallback behavior is expected and intended in the codebase.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using eq(table.workspaceId, ctx.workspace.id) to prevent cross-workspace access.

apps/dashboard/lib/trpc/routers/index.ts (3)

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses roleId as the property name for the role identifier, not id. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.

Learnt from: AkshayBandi027
PR: #2215
File: apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx:28-29
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In authorization/roles/[roleId]/update-role.tsx, the tag role-${role.id} is revalidated after updating a role to ensure that the caching mechanism is properly handled for roles.

apps/dashboard/app/(app)/versions/page.tsx (4)

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Update relevant anchors when modifying associated code.

Learnt from: ogzhanolguncu
PR: #3380
File: apps/dashboard/app/(app)/ratelimits/[namespaceId]/namespace-navbar.tsx:40-66
Timestamp: 2025-06-19T10:39:29.388Z
Learning: tRPC has built-in caching that prevents skeleton flashing during component re-renders and navigation, so concerns about !data || isLoading conditions causing loading state flashes are generally not needed.

apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (2)

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: ogzhanolguncu
PR: #3311
File: apps/dashboard/components/logs/llm-search/components/search-input.tsx:14-14
Timestamp: 2025-06-10T14:21:42.413Z
Learning: In Next.js applications, importing backend modules into frontend components causes bundling issues and can expose server-side code to the client bundle. For simple constants like limits, it's acceptable to duplicate the value rather than compromise the frontend/backend architectural separation.

apps/dashboard/app/(app)/projects/[projectId]/page.tsx (1)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (1)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (2)

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/trpc/routers/key/create.ts:11-14
Timestamp: 2025-06-02T11:09:58.791Z
Learning: In the unkey codebase, TypeScript and the env() function implementation already provide sufficient validation for environment variables, so additional runtime error handling for missing env vars is not needed.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

apps/dashboard/app/(app)/projects/page.tsx (4)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

go/deploy/Dockerfile.dev (9)

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{env,sh,yaml,yml,json,conf,ini} : All environment variables MUST follow the format UNKEY_<SERVICE_NAME>_VARNAME.

Learnt from: chronark
PR: #3638
File: deployment/docker-compose.yaml:81-94
Timestamp: 2025-07-22T09:02:12.495Z
Learning: The docker-compose.yaml file in deployment/ is specifically for development environments, not production. Kafka and other service configurations in this file should be optimized for development convenience rather than production security/hardening.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/Makefile : Use make install to build and install the binary with systemd unit from $SERVICE/contrib/systemd.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/*/contrib/systemd/** : Systemd unit files should be placed in <service>/contrib/systemd.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/Makefile : Never use go build for any of the assetmanagerd, billaged, builderd, metald binaries.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/{assetmanagerd,billaged,builderd,metald}/**/Makefile : Use make build to test that the binary builds.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/deploy/*/Makefile : Service-level makefile should be <service>/Makefile.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/Makefile : Global makefile should be Makefile at the repository root.

apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (1)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

go/demo_api/main.go (1)

Learnt from: Flo4604
PR: #2955
File: go/apps/api/routes/v2_identities_create_identity/handler.go:162-202
Timestamp: 2025-03-19T09:25:59.751Z
Learning: In the Unkey codebase, input validation for API endpoints is primarily handled through OpenAPI schema validation, which occurs before requests reach the handler code. For example, in the identities.createIdentity endpoint, minimum values for ratelimit duration and limit are defined in the OpenAPI schema rather than duplicating these checks in the handler.

🧬 Code Graph Analysis (10)
apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (2)
internal/icons/src/icons/folder-cloud.tsx (1)
  • FolderCloud (16-47)
internal/icons/src/icons/tag.tsx (1)
  • Tag (15-39)
apps/dashboard/lib/trpc/routers/index.ts (2)
apps/dashboard/lib/trpc/routers/deployment/index.ts (1)
  • deploymentRouter (7-12)
apps/dashboard/lib/trpc/routers/branch/index.ts (1)
  • branchRouter (5-8)
apps/dashboard/lib/trpc/routers/branch/index.ts (3)
apps/dashboard/lib/trpc/trpc.ts (1)
  • t (8-8)
apps/dashboard/lib/trpc/routers/branch/getByName.ts (1)
  • getByName (6-112)
apps/dashboard/lib/trpc/routers/branch/listByProject.ts (1)
  • listByProject (6-51)
apps/dashboard/app/(app)/versions/page.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/constants.ts (1)
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/types.ts (1)
  • DiffData (12-14)
apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (5)
apps/dashboard/lib/trpc/routers/index.ts (1)
  • router (120-313)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
internal/db/src/schema/branches.ts (1)
  • branches (6-23)
internal/ui/src/components/buttons/button.tsx (1)
  • Button (439-439)
internal/ui/src/components/form/select.tsx (4)
  • SelectTrigger (173-173)
  • SelectValue (172-172)
  • SelectContent (174-174)
  • SelectItem (176-176)
apps/dashboard/lib/trpc/routers/deployment/getById.ts (3)
apps/dashboard/lib/trpc/trpc.ts (3)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
packages/rbac/src/queries.ts (1)
  • and (52-56)
apps/dashboard/lib/trpc/routers/deployment/list.ts (2)
apps/dashboard/lib/trpc/trpc.ts (3)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
apps/dashboard/lib/trpc/routers/deployment/listByBranch.ts (3)
apps/dashboard/lib/trpc/trpc.ts (3)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
packages/rbac/src/queries.ts (1)
  • and (52-56)
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (4)
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/types.ts (2)
  • DiffData (12-14)
  • DiffChange (1-10)
internal/icons/src/icons/plus.tsx (1)
  • Plus (16-52)
internal/icons/src/icons/chevron-down.tsx (1)
  • ChevronDown (15-34)
internal/icons/src/icons/chevron-right.tsx (1)
  • ChevronRight (17-40)
🪛 GitHub Actions: autofix.ci
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx

[error] 81-81: Biome lint/style/useBlockStatements: Block statements are preferred in this position; wrap the statement with a JsBlockStatement.


[error] 82-82: Biome lint/style/useBlockStatements: Block statements are preferred in this position; wrap the statement with a JsBlockStatement.


[error] 321-321: Biome lint/style/useBlockStatements: Block statements are preferred in this position; wrap the statement with a JsBlockStatement.


[error] 322-322: Biome lint/style/useBlockStatements: Block statements are preferred in this position; wrap the statement with a JsBlockStatement.


[error] 324-325: Biome lint/style/useBlockStatements: Block statements are preferred in this position; wrap the statement with a JsBlockStatement.


[error] 326-326: Biome lint/style/useBlockStatements: Block statements are preferred in this position; wrap the statement with a JsBlockStatement.


[error] 495-495: Biome lint/style/useSelfClosingElements: JSX elements without children should be marked as self-closing.


[error] 542-549: Biome lint/a11y/useKeyWithClickEvents: Actions triggered using mouse events should have corresponding keyboard events for accessibility.


[error] 675-677: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input element.


[error] 701-703: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input element.


[error] 722-724: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input element.


[error] 810-819: Biome lint/a11y/useKeyWithClickEvents: Actions triggered using mouse events should have corresponding keyboard events for accessibility.


[error] 845-857: Biome lint/a11y/useKeyWithClickEvents: Actions triggered using mouse events should have corresponding keyboard events for accessibility.

apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx

[error] 242-242: Biome lint/style/useSelfClosingElements: JSX elements without children should be marked as self-closing.


[error] 709-711: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input element.


[error] 355-363: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.

apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx

[error] 98-98: Biome lint/style/useSelfClosingElements: JSX elements without children should be marked as self-closing.


[error] 27-27: Biome lint/correctness/noUnusedVariables: This interface 'Project' is unused.


[error] 63-63: Biome lint/correctness/noUnusedVariables: This variable 'selectedEnvironment' is unused.


[error] 237-245: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.

🪛 Hadolint (2.12.0)
go/deploy/Dockerfile.dev

[info] 132-132: Expressions don't expand in single quotes, use double quotes for that.

(SC2016)

🪛 Checkov (3.2.334)
go/deploy/Dockerfile.dev

[LOW] 1-213: Ensure that a user for the container has been created

(CKV_DOCKER_3)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (34)
go/deploy/Dockerfile.dev (1)

182-186: wait + set -e will terminate the container if any service exits non-zero

Because set -e is active, wait adopts the first non-zero status of the background PIDs.
If, for example, builderd crashes for an expected / recoverable reason, the whole container will stop immediately.

Please confirm this is the intended behaviour, or consider:

wait || EXIT_CODE=$?

and handle restart/back-off logic accordingly.

go/apps/api/openapi/openapi-generated.yaml (1)

2-2: Suppress generated timestamp in OpenAPI spec

The timestamp on line 2 of go/apps/api/openapi/openapi-generated.yaml changes every build and clutters diffs. Consider one of the following:

  • Update your OpenAPI generation template (or tool config) to omit or pin the timestamp so that running make openapi produces deterministic output.
  • Exclude go/apps/api/openapi/openapi-generated.yaml from source control and generate it on-demand in CI.

Example diff:

-# Generated at: 2025-07-25T17:21:46Z
+# Generated with `make openapi` – timestamp intentionally omitted for deterministic output
internal/db/src/schema/workspaces.ts (1)

55-60: Schema change looks good!

The addition of the deployments beta feature flag follows the established pattern for other beta features and is properly documented. The optional boolean type is appropriate for feature gating.

apps/dashboard/lib/trpc/routers/project/branches.ts (1)

40-42: Enhancement provides valuable project metadata.

Adding gitRepositoryUrl, createdAt, and updatedAt fields to the project object enriches the data available for UI components without breaking existing functionality.

apps/dashboard/lib/trpc/routers/branch/index.ts (1)

1-8: Clean router implementation following established patterns.

The branchRouter correctly aggregates the branch-related procedures and follows the same structure as other routers in the codebase. The implementation is straightforward and well-organized.

apps/dashboard/lib/trpc/routers/workspace/optIntoBeta.ts (2)

12-12: Beta feature enum correctly extended.

Adding "deployments" to the feature enum enables users to opt into the new deployments functionality through the existing beta opt-in mechanism.


25-28: Case handler follows established pattern.

The deployments case handler correctly sets the beta feature flag on the workspace context, maintaining consistency with other beta feature implementations.

apps/dashboard/lib/trpc/routers/index.ts (3)

40-40: Import correctly added for branch router.

The branchRouter import follows the established pattern and integrates cleanly with the existing imports.


114-114: Import correctly added for deployment router.

The deploymentRouter import is properly placed and follows the same pattern as other router imports.


311-312: Router integration exposes new functionality.

Adding the deployment and branch properties to the main router correctly exposes the new API endpoints, replacing the previous version-based approach with the new deployment-focused architecture.

apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (1)

12-12: Clean implementation of the Projects navigation item

The addition follows established patterns perfectly:

  • Proper use of segments.at(0) for active state checking (consistent with retrieved learnings)
  • Correct beta feature gating with workspace.betaFeatures.deployments
  • Appropriate use of the FolderCloud icon and Beta tag
  • Consistent positioning and structure with existing navigation items

Also applies to: 54-61

apps/dashboard/lib/trpc/routers/deployment/index.ts (1)

1-12: Well-structured router implementation

The deployment router follows established tRPC patterns with clear procedure organization and proper imports/exports. The structure is clean and maintainable.

apps/dashboard/lib/trpc/routers/deployment/list.ts (1)

5-51: Solid procedure implementation with proper patterns

The implementation follows good tRPC practices:

  • Correct middleware usage for authentication and workspace validation
  • Proper error handling with descriptive TRPC errors
  • Well-structured data mapping with appropriate field selection
  • Good use of database relations for project and branch data
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/types.ts (1)

1-14: Well-designed type definitions for diff functionality

The interfaces are well-structured with:

  • Comprehensive fields in DiffChange for representing API differences
  • Appropriate field types (strings for identifiers/text, number for level)
  • Good extensibility with the optional comment field
  • Clean wrapper pattern with DiffData for future expansion
apps/dashboard/app/(app)/versions/page.tsx (2)

5-6: Clean refactoring from versions to deployments

The component has been properly renamed and the API call updated to use the new deployment endpoint. The change maintains all existing functionality while updating the domain terminology.


92-95: Verify CLI command in the empty state

  • File: apps/dashboard/app/(app)/versions/page.tsx, lines 92–95
  • Current snippet:
    ./bin/unkey create --workspace-id=ws_local_root --project-id=YOUR_PROJECT_ID --branch=main
    

Please confirm that this is still the correct way to create deployments under the new workflow. If the CLI syntax or flags have changed (e.g. now uses unkey deploy create or different flag names), update the snippet to match the latest unkey CLI usage.

apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (4)

1-21: LGTM! Clean imports and proper component setup.

The imports are well-organized and the Props interface is properly typed for the component parameters.


22-32: Good state management and tRPC usage.

The component state is well-organized with clear variable names, and the tRPC query follows proper patterns.


34-56: Excellent conditional query implementation.

The use of the enabled flag prevents unnecessary API calls, and the handleCompare logic correctly constructs the comparison URL.


262-277: Perfect compare button implementation.

The disabled state logic ensures both deployments are selected before enabling comparison, providing clear user feedback.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/constants.ts (2)

1-4: Clean type import and constant declaration.

Good separation of concerns with proper TypeScript typing.


200-201: Proper constant structure completion.

The sample data structure is correctly closed.

apps/dashboard/lib/trpc/routers/branch/getByName.ts (3)

1-15: Proper procedure setup with authentication.

Good use of middleware and input validation following established patterns.


16-28: Excellent security validation.

Proper validation ensures the project exists and belongs to the current workspace, preventing unauthorized access.


102-112: Solid error handling implementation.

Proper error handling that preserves TRPC errors while catching unexpected exceptions with descriptive messages.

apps/dashboard/lib/trpc/routers/branch/listByProject.ts (1)

1-13: Consistent procedure setup.

Follows the same patterns as other tRPC procedures with proper authentication and validation.

apps/dashboard/lib/trpc/routers/deployment/listByBranch.ts (3)

1-14: Standard procedure setup.

Consistent with other tRPC procedures in the codebase.


15-44: Good validation and query logic.

The branch validation ensures security, and the query properly fetches and maps the data. The use of the versions table while returning deployments reflects the ongoing terminology migration mentioned in the PR.


45-55: Excellent error handling.

Proper preservation of TRPC errors while catching unexpected exceptions. This follows the correct pattern used in other well-implemented procedures.

apps/dashboard/lib/trpc/routers/deployment/getById.ts (1)

47-57: LGTM! Proper error handling implementation.

The error handling correctly preserves TRPCError types while wrapping unexpected errors, and includes appropriate logging.

apps/dashboard/app/(app)/projects/[projectId]/page.tsx (1)

58-534: Well-structured component with good error handling.

The component demonstrates good practices:

  • Comprehensive error and loading states
  • Proper TypeScript interfaces
  • Clean tab-based organization
  • Responsive design considerations
apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (1)

209-236: Good navigation implementation using Next.js router.

The component correctly uses Next.js router for navigation, following best practices for client-side routing.

apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (1)

84-87: JSX element should be self-closing

According to the linting rules, JSX elements without children should be self-closing.

-            Back to Projects
-          </Button>
+            Back to Projects
+          </Button>

Wait, I see the button has children. Let me look at line 98 specifically which the pipeline mentions.

⛔ Skipped due to learnings
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with `useMemo` for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (1)

80-87: Use block statements for better readability

According to the linting rules, block statements are preferred for multi-line conditions.

       if (filters.searchQuery) {
         const query = filters.searchQuery.toLowerCase();
         return (
           change.text.toLowerCase().includes(query) ||
           change.path.toLowerCase().includes(query) ||
           change.id.toLowerCase().includes(query)
         );
       }
⛔ Skipped due to learnings
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with `useMemo` for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the `FilterableCommand` component in `apps/www/components/glossary/search.tsx`, refactoring type definitions into an interface is not necessary at this time.
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the `apps/www/components/glossary/terms-stepper-mobile.tsx` file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In `apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx`, the resize handler is already debounced.
Learnt from: p6l-richard
PR: unkeyed/unkey#2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the `FilterableCommand` component in `apps/www/components/glossary/search.tsx`, adding error handling and loading states to the results list is not necessary.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use `AIDEV-NOTE:`, `AIDEV-TODO:`, `AIDEV-BUSINESS_RULE:`, or `AIDEV-QUESTION:` (all-caps prefix) as anchor comments aimed at AI and developers.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3401
File: apps/dashboard/app/(app)/logs/filters.query-params.ts:10-0
Timestamp: 2025-06-24T13:29:10.129Z
Learning: The `buildQueryParams` function in `apps/dashboard/app/(app)/logs/filters.query-params.ts` calls `useFilters()` hook inside it, but this is valid because the function is only called from within other React hooks, maintaining the Rules of Hooks compliance.
Learnt from: mcstepp
PR: unkeyed/unkey#3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:47-50
Timestamp: 2025-05-15T15:57:02.128Z
Learning: When reviewing code for Unkey, prefer using `Boolean()` over the double negation (`!!`) operator for boolean coercion, as their linter rules favor this pattern.

const [latest, previous] = deployments;
router.push(`/projects/${projectId}/diff/${previous.id}/${latest.id}`);
} else {
alert("At least two deployments are needed to compare. Please create more deployments first.");
Copy link
Contributor

@coderabbitai coderabbitai bot Jul 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace browser alerts with proper UI notifications.

Using alert() provides poor user experience. Consider using toast notifications or modal dialogs.

Replace alerts with a toast notification system:

-alert("At least two deployments are needed to compare. Please create more deployments first.");
+toast.error("At least two deployments are needed to compare. Please create more deployments first.");

Consider using a library like react-hot-toast or the UI library's built-in notification system.

Also applies to: 233-233

🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx
at lines 216 and 233, replace the use of browser alert() calls with a proper UI
notification system such as toast notifications or modal dialogs. Integrate a
toast notification library like react-hot-toast or use the existing UI library's
notification component to display these messages in a non-blocking,
user-friendly manner.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good catch

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcstepp Thanks! Using proper toast notifications will definitely provide a much better user experience than those jarring browser alerts. The existing UI patterns in the codebase should make it straightforward to implement a more polished notification system.


🧠 Learnings used
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use `AIDEV-NOTE:`, `AIDEV-TODO:`, `AIDEV-BUSINESS_RULE:`, or `AIDEV-QUESTION:` (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the `ButtonGroup` component (`apps/dashboard/components/ui/group-button.tsx`), avoid suggesting the use of `role="group"` in ARIA attributes.

@@ -41,70 +41,485 @@ func main() {
json.NewEncoder(w).Encode(response)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle JSON encoding errors

The JSON encoding error is not handled, which could lead to partial responses being sent.

-    json.NewEncoder(w).Encode(response)
+    if err := json.NewEncoder(w).Encode(response); err != nil {
+      log.Printf("Failed to encode response: %v", err)
+      http.Error(w, "Internal server error", http.StatusInternalServerError)
+      return
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
json.NewEncoder(w).Encode(response)
if err := json.NewEncoder(w).Encode(response); err != nil {
log.Printf("Failed to encode response: %v", err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
🤖 Prompt for AI Agents
In go/demo_api/main.go at line 41, the JSON encoding error from
json.NewEncoder(w).Encode(response) is not handled, which may cause partial or
incomplete responses. Modify the code to check the error returned by Encode, and
if an error occurs, handle it appropriately by logging the error and sending an
HTTP error response to the client.

@vercel vercel bot temporarily deployed to Preview – dashboard July 25, 2025 19:21 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

♻️ Duplicate comments (26)
apps/dashboard/lib/trpc/routers/branch/listByProject.ts (2)

45-51: Inconsistent error handling pattern.

This procedure catches all errors generically, while other procedures like getByName preserve TRPC errors. This inconsistency could hide important error information.


40-40: Consider making production branch detection more configurable.

The production branch detection using name matching is consistent with other procedures, but could be improved with a configurable approach or database field.

apps/dashboard/lib/trpc/routers/deployment/getById.ts (1)

30-34: Add workspace validation to the branch query.

The branch query should verify that the fetched branch belongs to the same workspace to prevent potential data exposure.

apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (2)

59-59: Use Next.js router instead of window.history.back().

Based on the codebase patterns, Next.js router should be used for client-side navigation to preserve client state and enable smoother transitions between pages.


76-261: Extract duplicate deployment selector UI into a reusable component.

The branch and deployment selector UI code is duplicated for the FROM and TO sections. Consider extracting this into a reusable component to reduce code duplication and improve maintainability.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (4)

8-8: Remove commented import.

The commented import for sampleDiffData should be removed to clean up the code.


93-93: Use Next.js router instead of window methods for navigation.

Replace window.history.back() and window.location.reload() with Next.js router methods for better client state preservation and smoother transitions.


21-651: Consider breaking down this large component for better maintainability.

This large component handles multiple responsibilities and could benefit from decomposition into smaller, focused components for better testability and maintainability.


107-313: Extract duplicate deployment selector UI into a reusable component.

The deployment selector UI is duplicated between different states and could be extracted into a reusable component to follow DRY principles.

apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1)

89-91: TODO comment needs tracking

Consider using a tracking system or GitHub issue for this TODO to ensure it's addressed before production deployment.

Would you like me to create a GitHub issue to track moving this URL to an environment variable?

apps/dashboard/app/(app)/projects/[projectId]/page.tsx (1)

149-151: Replace mock deployments with actual data

The deployments array is hardcoded as empty. This should fetch real deployment data.

apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (4)

215-216: Replace browser alerts with proper UI notifications

Using browser alerts provides a poor user experience.

Also applies to: 234-234


242-242: Fix self-closing element syntax

The loading spinner div should be self-closing.

-<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-brand" />
+<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-brand" />

709-716: Associate label with select element for accessibility

The label needs to be properly associated with the select element.

-<label className="block text-sm font-medium text-content-subtle mb-2">
+<label htmlFor="environment-select" className="block text-sm font-medium text-content-subtle mb-2">
   Environment Assignment
 </label>
-<select className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent bg-white text-content">
+<select id="environment-select" className="w-full px-3 py-2 border border-border rounded-lg focus:ring-2 focus:ring-brand focus:border-transparent bg-white text-content">

355-363: Add explicit button type to prevent form submission

Buttons without type attribute default to "submit" which can cause unintended form submissions.

 <button
+  type="button"
   key={key}
   onClick={() => setActiveTab(key as typeof activeTab)}
apps/dashboard/app/(app)/projects/page.tsx (2)

20-29: Move Project interface to shared types

This interface is likely used across multiple components and should be in a shared types file.


74-81: Extract slug generation to utility function

The slug generation logic could be reused in other parts of the application.

apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (5)

27-34: Remove unused Project interface

The Project interface is defined but never used in this component. It should be removed to keep the code clean.

-interface Project {
-  id: string;
-  name: string;
-  slug: string;
-  gitRepositoryUrl: string | null;
-  createdAt: number;
-  updatedAt: number | null;
-}

63-63: Remove unused selectedEnvironment state variable

The selectedEnvironment state variable is defined but never used in the component. Even though it's prefixed with underscore, it should be removed entirely.

-  const [selectedEnvironment, _setSelectedEnvironment] = useState<string>("all");

146-146: Replace mock data with actual API call

The versions data is currently mocked. Based on the PR being work-in-progress for demonstration, this should eventually be replaced with an actual tRPC query to fetch real version data.


237-248: Add explicit type prop to button elements

Button elements should have an explicit type to prevent unintended form submissions.

               <button
+                type="button"
                 key={key}
                 onClick={() => setActiveTab(key as typeof activeTab)}
                 className={`flex items-center gap-2 py-2 px-1 border-b-2 font-medium text-sm transition-colors ${
                   activeTab === key
                     ? "border-brand text-brand"
                     : "border-transparent text-content-subtle hover:text-content hover:border-border"
                 }`}
               >
                 <Icon className="w-4 h-4" />
                 {label}
               </button>

56-511: Component is too large - consider splitting into smaller components

This component is over 500 lines long and handles multiple responsibilities. Consider extracting each tab's content into its own component for better maintainability and testability.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (4)

33-927: Component is extremely large - urgent refactoring needed

This component is over 900 lines long and handles too many responsibilities. It should be broken down into smaller, focused components for better maintainability.


174-269: Consider replacing hardcoded demo data with dynamic implementation

The getBeforeSpec and getAfterSpec functions contain hardcoded data. While this is acceptable for POC/demonstration code as per project preferences, these should eventually be replaced with actual data from the API or props.


554-563: Add keyboard event handler for accessibility

Click handlers should have corresponding keyboard events for accessibility.

                     <div
                       onClick={() => {
                         setSelectedPath(change.path);
                         setSelectedOperation(change.operation);
                         setViewMode("side-by-side");
                       }}
+                      onKeyDown={(e) => {
+                        if (e.key === 'Enter' || e.key === ' ') {
+                          e.preventDefault();
+                          setSelectedPath(change.path);
+                          setSelectedOperation(change.operation);
+                          setViewMode("side-by-side");
+                        }
+                      }}
+                      role="button"
+                      tabIndex={0}
                       className="text-xs text-brand hover:text-brand/80 flex items-center space-x-1 cursor-pointer"
                     >

686-699: Associate label with input control

Labels must be associated with their corresponding input elements for accessibility.

-                      <label className="block text-sm font-medium text-content mb-2">
+                      <label htmlFor="search-changes" className="block text-sm font-medium text-content mb-2">
                         Search Changes
                       </label>
                       <div className="relative">
                         <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-content-subtle" />
                         <input
+                          id="search-changes"
                           type="text"
                           value={filters.searchQuery}
📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 01f9580 and c6643e4.

📒 Files selected for processing (16)
  • apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/page.tsx (2 hunks)
  • apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (2 hunks)
  • apps/dashboard/lib/trpc/routers/branch/getByName.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/branch/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/branch/listByProject.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/getById.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/listByBranch.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (17)
📓 Common learnings
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.
apps/dashboard/lib/trpc/routers/deployment/index.ts (2)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

apps/dashboard/lib/trpc/routers/branch/index.ts (2)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (4)

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

apps/dashboard/lib/trpc/routers/index.ts (6)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses roleId as the property name for the role identifier, not id. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.

Learnt from: chronark
PR: #2693
File: apps/api/src/routes/v1_keys_updateKey.ts:350-368
Timestamp: 2024-11-29T15:15:47.308Z
Learning: In apps/api/src/routes/v1_keys_updateKey.ts, the code intentionally handles externalId and ownerId separately for clarity. The ownerId field will be removed in the future, simplifying the code.

Learnt from: AkshayBandi027
PR: #2215
File: apps/dashboard/app/(app)/@breadcrumb/authorization/roles/[roleId]/page.tsx:28-29
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In authorization/roles/[roleId]/update-role.tsx, the tag role-${role.id} is revalidated after updating a role to ensure that the caching mechanism is properly handled for roles.

apps/dashboard/lib/trpc/routers/deployment/listByBranch.ts (2)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (17)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: MichaelUnkey
PR: #3425
File: apps/engineering/content/design/components/filter/control-cloud.examples.tsx:73-83
Timestamp: 2025-07-02T14:13:01.711Z
Learning: In apps/engineering/content/design/components/, when the RenderComponentWithSnippet component does not render code snippets correctly, use the customCodeSnippet prop to manually provide the correct JSX code as a string. This manual approach is necessary due to technical limitations in the automatic rendering mechanism.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: ogzhanolguncu
PR: #3311
File: apps/dashboard/components/logs/llm-search/components/search-input.tsx:14-14
Timestamp: 2025-06-10T14:21:42.413Z
Learning: In Next.js applications, importing backend modules into frontend components causes bundling issues and can expose server-side code to the client bundle. For simple constants like limits, it's acceptable to duplicate the value rather than compromise the frontend/backend architectural separation.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

apps/dashboard/lib/trpc/routers/deployment/getById.ts (5)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using eq(table.workspaceId, ctx.workspace.id) to prevent cross-workspace access.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.

apps/dashboard/lib/trpc/routers/branch/listByProject.ts (5)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (21)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/trpc/routers/key/create.ts:11-14
Timestamp: 2025-06-02T11:09:58.791Z
Learning: In the unkey codebase, TypeScript and the env() function implementation already provide sufficient validation for environment variables, so additional runtime error handling for missing env vars is not needed.

Learnt from: unrenamed
PR: #2652
File: apps/dashboard/components/dashboard/copy-button.tsx:38-38
Timestamp: 2024-11-08T11:40:17.737Z
Learning: The copyToClipboardWithMeta function currently has an unused _meta parameter. Consider removing it or utilizing it appropriately, especially when introducing the useCopyToClipboard hook.

Learnt from: unrenamed
PR: #2652
File: apps/www/components/particles.tsx:88-90
Timestamp: 2024-11-08T11:44:42.947Z
Learning: In React TypeScript components, if a function memoized with useCallback is not called elsewhere in the component, and a useEffect is used to invoke it, do not suggest removing the useEffect as it is necessary to execute the function.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: chronark
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/upsert-role/components/warning-callout.tsx:22-27
Timestamp: 2025-07-03T11:57:15.263Z
Learning: The target prop on InlineLink components in the @unkey/ui package is a boolean prop, not a string. When target is truthy, it automatically sets target="_blank" and rel="noopener noreferrer" on the underlying anchor element. Using just target (equivalent to target={true}) is the correct way to make the link open in a new tab.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

apps/dashboard/app/(app)/projects/[projectId]/page.tsx (3)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (17)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: ogzhanolguncu
PR: #3311
File: apps/dashboard/components/logs/llm-search/components/search-input.tsx:14-14
Timestamp: 2025-06-10T14:21:42.413Z
Learning: In Next.js applications, importing backend modules into frontend components causes bundling issues and can expose server-side code to the client bundle. For simple constants like limits, it's acceptable to duplicate the value rather than compromise the frontend/backend architectural separation.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: ogzhanolguncu
PR: #2866
File: apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/log-details/components/log-footer.tsx:85-98
Timestamp: 2025-02-05T12:56:44.873Z
Learning: The RequestResponseDetails component in the ratelimit logs UI already handles empty content cases by preventing rendering when content is empty, so no additional empty state handling is needed in the parent components.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: MichaelUnkey
PR: #2810
File: internal/ui/src/components/date-time/components/time-split.tsx:10-14
Timestamp: 2025-01-22T16:51:59.978Z
Learning: The DateTime component in internal/ui/src/components/date-time/components/time-split.tsx already includes sufficient validation through handleChange and handleBlur functions, making additional runtime validation unnecessary.

Learnt from: chronark
PR: #2792
File: apps/dashboard/app/(app)/settings/user/update-user-email.tsx:76-78
Timestamp: 2025-01-07T19:55:33.055Z
Learning: In the Unkey codebase, the Empty component can be used as a container for loading states, as demonstrated in the UpdateUserEmail component where it wraps the Loading component.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (10)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/trpc/routers/key/create.ts:11-14
Timestamp: 2025-06-02T11:09:58.791Z
Learning: In the unkey codebase, TypeScript and the env() function implementation already provide sufficient validation for environment variables, so additional runtime error handling for missing env vars is not needed.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.

Learnt from: chronark
PR: #2544
File: apps/api/src/pkg/env.ts:4-6
Timestamp: 2024-10-23T12:05:31.121Z
Learning: The cloudflareRatelimiter type definition in apps/api/src/pkg/env.ts should not have its interface changed; it should keep the limit method returning Promise<{ success: boolean }> without additional error properties.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: Flo4604
PR: #3421
File: go/apps/api/openapi/openapi.yaml:196-200
Timestamp: 2025-07-03T05:58:10.699Z
Learning: In the Unkey codebase, OpenAPI 3.1 is used, which allows sibling keys (such as description) alongside $ref in schema objects. Do not flag this as an error in future reviews.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

apps/dashboard/lib/trpc/routers/branch/getByName.ts (4)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (18)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx:7-43
Timestamp: 2025-05-15T16:09:49.243Z
Learning: For type safety issues involving any type assertions, the team prefers to address these systematically with linter updates rather than fixing them individually in code reviews.

Learnt from: Flo4604
PR: #3421
File: go/apps/api/openapi/openapi.yaml:196-200
Timestamp: 2025-07-03T05:58:10.699Z
Learning: In the Unkey codebase, OpenAPI 3.1 is used, which allows sibling keys (such as description) alongside $ref in schema objects. Do not flag this as an error in future reviews.

Learnt from: ogzhanolguncu
PR: #3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/trpc/routers/key/create.ts:11-14
Timestamp: 2025-06-02T11:09:58.791Z
Learning: In the unkey codebase, TypeScript and the env() function implementation already provide sufficient validation for environment variables, so additional runtime error handling for missing env vars is not needed.

apps/dashboard/app/(app)/projects/page.tsx (15)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx:7-43
Timestamp: 2025-05-15T16:09:49.243Z
Learning: For type safety issues involving any type assertions, the team prefers to address these systematically with linter updates rather than fixing them individually in code reviews.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: ogzhanolguncu
PR: #3380
File: apps/dashboard/app/(app)/ratelimits/[namespaceId]/namespace-navbar.tsx:40-66
Timestamp: 2025-06-19T10:39:29.388Z
Learning: tRPC has built-in caching that prevents skeleton flashing during component re-renders and navigation, so concerns about !data || isLoading conditions causing loading state flashes are generally not needed.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (23)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: MichaelUnkey
PR: #3425
File: apps/engineering/content/design/components/filter/control-cloud.examples.tsx:73-83
Timestamp: 2025-07-02T14:13:01.711Z
Learning: In apps/engineering/content/design/components/, when the RenderComponentWithSnippet component does not render code snippets correctly, use the customCodeSnippet prop to manually provide the correct JSX code as a string. This manual approach is necessary due to technical limitations in the automatic rendering mechanism.

Learnt from: ogzhanolguncu
PR: #3528
File: apps/dashboard/app/(app)/settings/team/role-switcher.tsx:60-76
Timestamp: 2025-07-11T12:57:39.875Z
Learning: In the Select component from @unkey/ui, when fixing chevron overflow/positioning issues, applying w-fit directly to the SelectTrigger doesn't resolve the problem. A wrapper div with w-fit class around the entire Select component is required to properly contain the chevron within the trigger area.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:39-50
Timestamp: 2024-10-23T16:19:59.657Z
Learning: Debouncing of the scroll handler is not needed yet in the function TermsRolodexDesktop in apps/www/components/glossary/terms-rolodex-desktop.tsx.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: unrenamed
PR: #2652
File: apps/www/components/particles.tsx:88-90
Timestamp: 2024-11-08T11:44:42.947Z
Learning: In React TypeScript components, if a function memoized with useCallback is not called elsewhere in the component, and a useEffect is used to invoke it, do not suggest removing the useEffect as it is necessary to execute the function.

Learnt from: MichaelUnkey
PR: #3439
File: apps/engineering/content/design/components/dialogs/dialog-container.mdx:103-111
Timestamp: 2025-07-08T12:47:17.841Z
Learning: In component documentation files (e.g., .mdx files in apps/engineering/content/design/components/), the Accessibility section is included in the standard template and should not be removed from component documentation.

Learnt from: unrenamed
PR: #2652
File: apps/dashboard/components/dashboard/copy-button.tsx:38-38
Timestamp: 2024-11-08T11:40:17.737Z
Learning: The copyToClipboardWithMeta function currently has an unused _meta parameter. Consider removing it or utilizing it appropriately, especially when introducing the useCopyToClipboard hook.

Learnt from: chronark
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/upsert-role/components/warning-callout.tsx:22-27
Timestamp: 2025-07-03T11:57:15.263Z
Learning: The target prop on InlineLink components in the @unkey/ui package is a boolean prop, not a string. When target is truthy, it automatically sets target="_blank" and rel="noopener noreferrer" on the underlying anchor element. Using just target (equivalent to target={true}) is the correct way to make the link open in a new tab.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:32-36
Timestamp: 2025-04-22T17:33:28.162Z
Learning: In the Unkey dashboard UI for delete protection, the button/link to initiate the process is labeled "Disable Delete Protection" while the confirmation button is labeled "Disable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:21-24
Timestamp: 2025-04-22T17:34:04.438Z
Learning: In the Unkey dashboard UI for enabling delete protection, the button/link to initiate the process is labeled "Enable Delete Protection" while the confirmation button is labeled "Enable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

🪛 GitHub Actions: autofix.ci
apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx

[error] 27-27: Biome lint/correctness/noUnusedVariables: This interface 'Project' is unused.


[error] 237-245: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.

apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx

[error] 709-711: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input. Add a 'for' or 'htmlFor' attribute or nest the input inside the label.


[error] 355-363: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx

[error] 553-560: Biome lint/a11y/useKeyWithClickEvents: onClick mouse event must be accompanied by keyboard events (onKeyUp, onKeyDown, or onKeyPress) for accessibility.


[error] 686-688: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input. Add a 'for' or 'htmlFor' attribute or nest the input inside the label.


[error] 712-714: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input. Add a 'for' or 'htmlFor' attribute or nest the input inside the label.


[error] 733-735: Biome lint/a11y/noLabelWithoutControl: A form label must be associated with an input. Add a 'for' or 'htmlFor' attribute or nest the input inside the label.


[error] 821-830: Biome lint/a11y/useKeyWithClickEvents: onClick mouse event must be accompanied by keyboard events (onKeyUp, onKeyDown, or onKeyPress) for accessibility.


[error] 856-868: Biome lint/a11y/useKeyWithClickEvents: onClick mouse event must be accompanied by keyboard events (onKeyUp, onKeyDown, or onKeyPress) for accessibility.


[error] 890-898: Biome lint/a11y/useKeyWithClickEvents: onClick mouse event must be accompanied by keyboard events (onKeyUp, onKeyDown, or onKeyPress) for accessibility.


[error] 389-401: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.


[error] 459-459: Biome lint/suspicious/noArrayIndexKey: Avoid using the index of an array as key property in an element to prevent issues with order changes and component state.


[error] 475-483: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.


[error] 588-596: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.


[error] 601-609: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.


[error] 614-622: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.


[error] 668-671: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.


[error] 701-706: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.


[error] 755-762: Biome lint/a11y/useButtonType: Provide an explicit type prop for the button element to avoid unintended form submission.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (16)
apps/dashboard/lib/trpc/routers/index.ts (1)

40-41: LGTM! Clean router integration following established patterns.

The addition of branchRouter and deploymentRouter correctly replaces the previous versionRouter, aligning with the migration from "versions" to "deployments" terminology. The integration follows the same pattern as other routers in the codebase.

Also applies to: 311-312

apps/dashboard/lib/trpc/routers/branch/index.ts (1)

1-8: LGTM! Clean and well-structured router implementation.

The branchRouter follows tRPC conventions and provides appropriate procedures for branch management (getByName and listByProject). The structure is consistent with other routers in the codebase.

apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (1)

4-4: LGTM! Proper navigation implementation with beta feature controls.

The Projects navigation item is correctly implemented:

  • Uses segments.at(0) for safe array access (consistent with codebase patterns)
  • Properly gates visibility behind workspace.betaFeatures.deployments
  • Includes appropriate Beta tag for user awareness
  • Positioned logically in the navigation flow

Also applies to: 54-61

apps/dashboard/lib/trpc/routers/deployment/index.ts (1)

1-12: LGTM! Well-organized deployment router with comprehensive procedures.

The deploymentRouter provides a complete set of deployment management procedures:

  • list and listByBranch for querying deployments
  • getById for detailed deployment information
  • getOpenApiDiff for API comparison functionality

The structure follows tRPC conventions and is consistent with other routers in the codebase.

apps/dashboard/lib/trpc/routers/branch/getByName.ts (1)

6-115: LGTM! Solid procedure implementation appropriate for demonstration purposes.

The getByName procedure is well-implemented with:

  • Proper authentication middleware and input validation
  • Comprehensive error handling with appropriate TRPCError usage
  • Well-structured database queries with relations
  • Appropriate data transformation for UI consumption

The mock data usage (gitCommitMessage, buildDuration, lastCommitAuthor) aligns with the PR's demonstration/POC nature and previous feedback indicating this approach is acceptable for the current scope.

apps/dashboard/lib/trpc/routers/branch/listByProject.ts (1)

14-33: LGTM! Solid project validation and data fetching logic.

The project existence validation with workspace scoping is well-implemented, and the branch fetching with proper ordering follows good practices.

apps/dashboard/lib/trpc/routers/deployment/listByBranch.ts (3)

45-55: Excellent error handling pattern.

The error handling correctly preserves TRPC errors while wrapping unexpected errors. This is the proper pattern that should be followed consistently across procedures.


17-27: Good branch validation with workspace scoping.

The branch existence check properly validates that the branch belongs to the current workspace, preventing cross-workspace data access.


30-33: Querying versions table is intentional per the learning context.

Based on the retrieved learnings, this procedure intentionally queries the versions table rather than a deployments table, as table renaming requires a database migration that was deferred for this UI-focused PR.

apps/dashboard/lib/trpc/routers/deployment/getById.ts (2)

17-20: Proper deployment validation with workspace scoping.

The deployment query correctly validates workspace membership, following security best practices.


51-61: Excellent error handling implementation.

The error handling properly preserves TRPC errors while wrapping unexpected errors with appropriate error codes and messages.

apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (2)

23-39: Excellent data fetching with conditional enabling.

The tRPC queries are properly structured with conditional enabling based on selected branches. This prevents unnecessary API calls and follows good practices.


41-47: Clean navigation logic for comparison.

The handleCompare function properly validates selections before navigation and constructs the URL correctly.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (3)

317-325: Well-structured diff data fetching.

The tRPC query for OpenAPI diff data is properly structured with clear parameter mapping and follows good practices for data fetching.


61-71: Smart branch synchronization logic.

The useEffect hooks properly synchronize branch selections based on deployment details from URL parameters, ensuring the UI stays consistent with the selected deployments.


636-647: Clean integration with DiffViewer component.

The DiffViewer integration is well-implemented with proper data passing and fallback values for display identifiers.

Comment on lines +84 to +78
<Button variant="outline">
<ArrowLeft className="w-4 h-4 mr-2" />
Back to Projects
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add navigation handlers to "Back to Projects" buttons

The "Back to Projects" buttons don't have onClick handlers. They should navigate to the projects list page.

+import { useRouter } from "next/navigation";

+const router = useRouter();

-          <Button variant="outline">
+          <Button variant="outline" onClick={() => router.push('/projects')}>
             <ArrowLeft className="w-4 h-4 mr-2" />
             Back to Projects
           </Button>

Also applies to: 114-116, 134-136

🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx at lines
84-87, 114-116, and 134-136, the "Back to Projects" buttons lack onClick
handlers for navigation. Add onClick handlers to these buttons that use the
router or navigation method to redirect users to the projects list page when
clicked.

Comment on lines +117 to +110
<Button variant="primary" onClick={() => window.location.reload()}>
Try Again
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Next.js router instead of window.location.reload()

Based on the codebase conventions, use Next.js router for navigation instead of window.location.reload() to preserve client state.

+import { useRouter } from "next/navigation";

+const router = useRouter();

-            <Button variant="primary" onClick={() => window.location.reload()}>
+            <Button variant="primary" onClick={() => router.refresh()}>
               Try Again
             </Button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Button variant="primary" onClick={() => window.location.reload()}>
Try Again
</Button>
import { useRouter } from "next/navigation";
function BranchesPage() {
const router = useRouter();
// … other component code …
<Button variant="primary" onClick={() => router.refresh()}>
Try Again
</Button>
// … other component code …
}
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx around lines
117 to 119, replace the use of window.location.reload() with Next.js router
navigation to reload the page. Import the Next.js router hook (useRouter) and
use router.replace(router.asPath) inside the onClick handler of the Button to
reload the current page while preserving client state.

Comment on lines +149 to +158
const getOperationColor = (operation: string) => {
const colors: Record<string, string> = {
GET: "bg-gray-100 text-gray-800 border border-gray-200",
POST: "bg-brand text-brand-foreground border border-gray-200",
PUT: "bg-amber-2 text-amber-11 border border-gray-200",
PATCH: "bg-gray-200 text-gray-700 border border-gray-300",
DELETE: "bg-red-2 text-red-11 border border-gray-200",
};
return colors[operation] || "bg-gray-100 text-gray-800 border border-gray-200";
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Extract color and style utilities to constants

The color utility functions contain hardcoded values that could be extracted to constants for better maintainability.

// Consider extracting to a separate constants file
const OPERATION_COLORS = {
  GET: "bg-gray-100 text-gray-800 border border-gray-200",
  POST: "bg-brand text-brand-foreground border border-gray-200",
  PUT: "bg-amber-2 text-amber-11 border border-gray-200",
  PATCH: "bg-gray-200 text-gray-700 border border-gray-300",
  DELETE: "bg-red-2 text-red-11 border border-gray-200",
  DEFAULT: "bg-gray-100 text-gray-800 border border-gray-200"
} as const;

const SEVERITY_COLORS = {
  DEFAULT: "border-l-4 border-l-brand bg-background-subtle hover:bg-gray-100"
} as const;

Also applies to: 138-147, 127-136

🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx
around lines 149 to 158, the color utility function getOperationColor uses
hardcoded style strings. Extract these style strings into a separate constant
object, such as OPERATION_COLORS, ideally in a constants file or at the top of
the component file. Replace the inline color mappings in getOperationColor with
references to this constant to improve maintainability and reuse. Apply the same
extraction approach to similar color/style mappings found around lines 127-136
and 138-147.

Comment on lines +393 to +401
if (typeof window !== "undefined" && selectedPath && selectedOperation) {
const beforeCode = getBeforeSpec(selectedPath, selectedOperation);
const afterCode = getAfterSpec(selectedPath, selectedOperation);
if (navigator.clipboard) {
navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`);
}
}
}}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for clipboard API

The clipboard API usage should include error handling for better user experience.

             onClick={() => {
               if (typeof window !== "undefined" && selectedPath && selectedOperation) {
                 const beforeCode = getBeforeSpec(selectedPath, selectedOperation);
                 const afterCode = getAfterSpec(selectedPath, selectedOperation);
                 if (navigator.clipboard) {
-                  navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`);
+                  navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`)
+                    .catch((err) => {
+                      console.error('Failed to copy to clipboard:', err);
+                      // Consider showing a toast notification
+                    });
                 }
               }
             }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (typeof window !== "undefined" && selectedPath && selectedOperation) {
const beforeCode = getBeforeSpec(selectedPath, selectedOperation);
const afterCode = getAfterSpec(selectedPath, selectedOperation);
if (navigator.clipboard) {
navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`);
}
}
}}
onClick={() => {
if (typeof window !== "undefined" && selectedPath && selectedOperation) {
const beforeCode = getBeforeSpec(selectedPath, selectedOperation);
const afterCode = getAfterSpec(selectedPath, selectedOperation);
if (navigator.clipboard) {
navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`)
.catch((err) => {
console.error('Failed to copy to clipboard:', err);
// Consider showing a toast notification
});
}
}
}}
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx
around lines 393 to 400, the clipboard API call to writeText lacks error
handling. Wrap the navigator.clipboard.writeText call in a try-catch block or
use the promise's catch method to handle any errors that may occur during the
clipboard write operation, and provide appropriate feedback or logging for
failures.

Comment on lines +146 to +148
<Button onClick={() => setShowCreateForm(false)} variant="ghost" size="sm">
</Button>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Use proper close icon instead of text

Using "✕" as text is not ideal for accessibility and consistency.

-<Button onClick={() => setShowCreateForm(false)} variant="ghost" size="sm">
-
-</Button>
+<Button onClick={() => setShowCreateForm(false)} variant="ghost" size="sm">
+  <X className="w-4 h-4" />
+</Button>

Note: You'll need to import X from lucide-react:

 import {
   Activity,
   ExternalLink,
   Eye,
   Filter,
   FolderOpen,
   FolderPlus,
   GitBranch,
   Github,
   Plus,
   Search,
   Tag,
+  X,
 } from "lucide-react";
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/page.tsx around lines 146 to 148, replace
the text "✕" inside the Button component with the proper close icon component
`X` imported from lucide-react. Import `X` at the top of the file and use it as
the Button's child to improve accessibility and maintain UI consistency.

Comment on lines +28 to +30
// TODO: add this column
//gitCommitMessage: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Commented code should be removed or documented

These commented lines for gitCommitMessage appear in multiple places. If this field is planned for future implementation, consider adding a more descriptive comment explaining when it will be added.

-            // TODO: add this column
-            //gitCommitMessage: true,
+            // TODO: gitCommitMessage field pending database migration
+            // Tracked in issue #XXX
+            //gitCommitMessage: true,

Also applies to: 55-55

🤖 Prompt for AI Agents
In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts at lines 28-29
and line 55, the commented-out code for the gitCommitMessage field should either
be removed or replaced with a clear comment explaining the planned future
implementation and timeline. Update these lines to include a descriptive comment
about when and why gitCommitMessage will be added, or remove the commented code
if it is no longer relevant.

@vercel vercel bot temporarily deployed to Preview – dashboard July 25, 2025 19:27 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (16)
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (4)

8-9: Remove commented import.

-// import { sampleDiffData } from "./constants";
 import { DiffViewer } from "./components/client";

93-93: Use Next.js router instead of window methods for navigation.

Replace window.history.back() and window.location.reload() with Next.js router methods for better client state preservation and smoother transitions.

For back navigation:

-onClick={() => window.history.back()}
+onClick={() => router.back()}

For reload:

-onClick={() => window.location.reload()}
+onClick={() => router.refresh()}

Also applies to: 357-357, 376-376, 379-379, 396-396


107-303: Extract duplicate deployment selector UI into a reusable component.

The deployment selector UI is duplicated between the "no deployment IDs" case and the success state. This violates the DRY principle and makes maintenance harder.

Create a new component DeploymentSelector and use it in both places:

interface DeploymentSelectorProps {
  branches: Branch[];
  branchesLoading: boolean;
  selectedFromBranch: string;
  selectedToBranch: string;
  selectedFromDeployment: string;
  selectedToDeployment: string;
  fromDeployments: Deployment[];
  toDeployments: Deployment[];
  fromDeploymentsLoading: boolean;
  toDeploymentsLoading: boolean;
  onFromBranchChange: (value: string) => void;
  onToBranchChange: (value: string) => void;
  onFromDeploymentChange: (value: string) => void;
  onToDeploymentChange: (value: string) => void;
  onCompare: () => void;
}

function DeploymentSelector(props: DeploymentSelectorProps) {
  // Move the selector UI code here
}

Then use it in both places to eliminate duplication.

Also applies to: 428-631


21-651: Consider breaking down this large component for better maintainability.

This 600+ line component handles multiple responsibilities and could benefit from decomposition into smaller, focused components.

Consider extracting:

  1. Custom hooks for data fetching logic (useDeploymentComparison)
  2. Separate components for loading, error, and empty states
  3. Header component with navigation
  4. DeploymentInfo component for the deployment details display

This would improve testability, reusability, and maintainability.

apps/dashboard/app/(app)/projects/[projectId]/page.tsx (2)

141-143: Implement actual deployments data fetching.

The deployments data is currently mocked as an empty array. This should fetch real data from the deployment API.

Would you like me to implement the tRPC query for fetching deployments? I can create a query similar to the branches query that's already in place.


271-272: Deployment count is hardcoded to zero

The deployment count shows 0 regardless of actual data since it's using the mock empty array.

This will be resolved once the deployments data is fetched from the API as suggested in the previous comment.

apps/dashboard/app/(app)/projects/page.tsx (4)

20-29: Consider moving the Project interface to a shared types file

Since this interface represents a core data structure that's likely used across multiple components, it would be better to define it in a shared types file (e.g., types/project.ts) for reusability and consistency.


74-81: Extract the generateSlug function to a utility module

This slug generation logic could be useful in other parts of the application. Consider moving it to a shared utilities file.

-  const generateSlug = (name: string): string => {
-    return name
-      .toLowerCase()
-      .replace(/[^a-z0-9\s-]/g, "")
-      .replace(/\s+/g, "-")
-      .replace(/-+/g, "-")
-      .replace(/^-|-$/g, "");
-  };

Create a new file utils/slug.ts:

export const generateSlug = (name: string): string => {
  return name
    .toLowerCase()
    .replace(/[^a-z0-9\s-]/g, "")
    .replace(/\s+/g, "-")
    .replace(/-+/g, "-")
    .replace(/^-|-$/g, "");
};

146-148: Use proper close icon instead of text

Using "✕" as text is not ideal for accessibility and consistency.

-<Button onClick={() => setShowCreateForm(false)} variant="ghost" size="sm">
-
-</Button>
+<Button onClick={() => setShowCreateForm(false)} variant="ghost" size="sm">
+  <X className="w-4 h-4" />
+</Button>

Note: You'll need to import X from lucide-react:

 import {
   Activity,
   ExternalLink,
   Eye,
   Filter,
   FolderOpen,
   FolderPlus,
   GitBranch,
   Github,
   Plus,
   Search,
   Tag,
+  X,
 } from "lucide-react";

223-243: Consider extracting the loading skeleton into a separate component

The loading skeleton pattern is repeated 8 times and could be extracted into a reusable component for better maintainability.

function ProjectCardSkeleton() {
  return (
    <div className="bg-white rounded-xl shadow-sm border border-border p-6 animate-pulse">
      <div className="flex items-start justify-between mb-4">
        <div className="flex-1">
          <div className="h-5 bg-background-subtle rounded mb-2 w-3/4" />
          <div className="h-4 bg-background-subtle rounded w-1/2" />
        </div>
        <div className="w-5 h-5 bg-background-subtle rounded" />
      </div>
      <div className="h-16 bg-background-subtle rounded mb-4" />
      <div className="space-y-2 mb-4">
        <div className="h-3 bg-background-subtle rounded w-full" />
        <div className="h-3 bg-background-subtle rounded w-2/3" />
      </div>
      <div className="h-8 bg-background-subtle rounded" />
    </div>
  );
}

Then use it:

-            {Array.from({ length: 8 }).map((_, i) => (
-              <div
-                key={`skeleton-${i}`}
-                className="bg-white rounded-xl shadow-sm border border-border p-6 animate-pulse"
-              >
-                {/* ... skeleton content ... */}
-              </div>
-            ))}
+            {Array.from({ length: 8 }).map((_, i) => (
+              <ProjectCardSkeleton key={`skeleton-${i}`} />
+            ))}
apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (6)

54-54: Remove unused selectedEnvironment state variable

The selectedEnvironment state variable is defined but never used in the component.

-  const [selectedEnvironment, _setSelectedEnvironment] = useState<string>("all");

108-110: Use Next.js router instead of window.location.reload()

Based on the codebase conventions, use Next.js router for navigation instead of window.location.reload() to preserve client state.

+import { useRouter } from "next/navigation";

export default function BranchDetailPage(): JSX.Element {
+ const router = useRouter();
  // ... existing code ...

-            <Button variant="primary" onClick={() => window.location.reload()}>
+            <Button variant="primary" onClick={() => router.refresh()}>
               Try Again
             </Button>

137-137: Replace mock data with actual API call

The versions data is currently mocked. This should be replaced with an actual tRPC query to fetch real version data.

-  // Mock versions data - replace with actual tRPC call
-  const versions: Version[] = [];
+  const { data: versionsData } = trpc.project.versions.useQuery(
+    { projectId },
+    { enabled: !!projectId }
+  );
+  const versions: Version[] = versionsData?.versions || [];

Would you like me to help implement the proper tRPC query for fetching versions?


75-78: Add navigation handlers to "Back to Projects" buttons

The "Back to Projects" buttons don't have onClick handlers. They should navigate to the projects list page.

+import { useRouter } from "next/navigation";

export default function BranchDetailPage(): JSX.Element {
+ const router = useRouter();
  // ... existing code ...

-          <Button variant="outline">
+          <Button variant="outline" onClick={() => router.push('/projects')}>
             <ArrowLeft className="w-4 h-4 mr-2" />
             Back to Projects
           </Button>

Also applies to: 104-107, 124-127


228-241: Add explicit type prop to button elements

Button elements should have an explicit type to prevent unintended form submissions.

               <button
+                type="button"
                 key={key}
                 onClick={() => setActiveTab(key as typeof activeTab)}
                 className={`flex items-center gap-2 py-2 px-1 border-b-2 font-medium text-sm transition-colors ${
                   activeTab === key
                     ? "border-brand text-brand"
                     : "border-transparent text-content-subtle hover:text-content hover:border-border"
                 }`}
               >
                 <Icon className="w-4 h-4" />
                 {label}
               </button>

47-503: Component is too large - consider splitting into smaller components

This component is over 500 lines long and handles multiple responsibilities. Consider extracting:

  • Tab navigation component
  • Overview tab content
  • Branches tab content
  • Versions tab content
  • Settings tab content
  • Stats cards section

Breaking this into smaller components will improve maintainability, testability, and reusability. Each tab's content could be its own component that receives the necessary data as props.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6643e4 and 32ad865.

📒 Files selected for processing (4)
  • apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/page.tsx (2 hunks)
🧰 Additional context used
🧠 Learnings (5)
📓 Common learnings
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (17)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: MichaelUnkey
PR: #3425
File: apps/engineering/content/design/components/filter/control-cloud.examples.tsx:73-83
Timestamp: 2025-07-02T14:13:01.711Z
Learning: In apps/engineering/content/design/components/, when the RenderComponentWithSnippet component does not render code snippets correctly, use the customCodeSnippet prop to manually provide the correct JSX code as a string. This manual approach is necessary due to technical limitations in the automatic rendering mechanism.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: ogzhanolguncu
PR: #3311
File: apps/dashboard/components/logs/llm-search/components/search-input.tsx:14-14
Timestamp: 2025-06-10T14:21:42.413Z
Learning: In Next.js applications, importing backend modules into frontend components causes bundling issues and can expose server-side code to the client bundle. For simple constants like limits, it's acceptable to duplicate the value rather than compromise the frontend/backend architectural separation.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx (23)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/trpc/routers/key/create.ts:11-14
Timestamp: 2025-06-02T11:09:58.791Z
Learning: In the unkey codebase, TypeScript and the env() function implementation already provide sufficient validation for environment variables, so additional runtime error handling for missing env vars is not needed.

Learnt from: unrenamed
PR: #2652
File: apps/dashboard/components/dashboard/copy-button.tsx:38-38
Timestamp: 2024-11-08T11:40:17.737Z
Learning: The copyToClipboardWithMeta function currently has an unused _meta parameter. Consider removing it or utilizing it appropriately, especially when introducing the useCopyToClipboard hook.

Learnt from: unrenamed
PR: #2652
File: apps/www/components/particles.tsx:88-90
Timestamp: 2024-11-08T11:44:42.947Z
Learning: In React TypeScript components, if a function memoized with useCallback is not called elsewhere in the component, and a useEffect is used to invoke it, do not suggest removing the useEffect as it is necessary to execute the function.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: chronark
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/upsert-role/components/warning-callout.tsx:22-27
Timestamp: 2025-07-03T11:57:15.263Z
Learning: The target prop on InlineLink components in the @unkey/ui package is a boolean prop, not a string. When target is truthy, it automatically sets target="_blank" and rel="noopener noreferrer" on the underlying anchor element. Using just target (equivalent to target={true}) is the correct way to make the link open in a new tab.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

apps/dashboard/app/(app)/projects/[projectId]/page.tsx (7)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: ogzhanolguncu
PR: #2707
File: apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts:63-63
Timestamp: 2024-12-05T13:27:55.555Z
Learning: In apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts, when determining the maximum number of rate limit overrides (max), the intentional use of const max = hasWorkspaceAccess("ratelimitOverrides", namespace.workspace) || 5; allows max to fall back to 5 when hasWorkspaceAccess returns 0 or false. This fallback behavior is expected and intended in the codebase.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

apps/dashboard/app/(app)/projects/page.tsx (19)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx:7-43
Timestamp: 2025-05-15T16:09:49.243Z
Learning: For type safety issues involving any type assertions, the team prefers to address these systematically with linter updates rather than fixing them individually in code reviews.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: ogzhanolguncu
PR: #3380
File: apps/dashboard/app/(app)/ratelimits/[namespaceId]/namespace-navbar.tsx:40-66
Timestamp: 2025-06-19T10:39:29.388Z
Learning: tRPC has built-in caching that prevents skeleton flashing during component re-renders and navigation, so concerns about !data || isLoading conditions causing loading state flashes are generally not needed.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: MichaelUnkey
PR: #3439
File: apps/engineering/content/design/components/dialogs/dialog-container.mdx:103-111
Timestamp: 2025-07-08T12:47:17.841Z
Learning: In component documentation files (e.g., .mdx files in apps/engineering/content/design/components/), the Accessibility section is included in the standard template and should not be removed from component documentation.

Learnt from: chronark
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/components/log-footer.tsx:58-61
Timestamp: 2024-12-03T14:21:19.543Z
Learning: For the "Outcome" field in the LogFooter component (apps/dashboard/app/(app)/logs/components/log-details/components/log-footer.tsx), default to "N/A" instead of "VALID" when handling null values to avoid confusing customers.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)

<ArrowLeft className="w-4 h-4" />
Back to Projects
</a>
<Button variant="primary" onClick={() => window.location.reload()}>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use Next.js router instead of window.location.reload().

Based on the codebase conventions, use Next.js router for navigation instead of window.location.reload() to preserve client state.

+import { useRouter } from "next/navigation";

export default function ProjectDetailPage(): JSX.Element {
+ const router = useRouter();
  // ... existing code ...

-            <Button variant="primary" onClick={() => window.location.reload()}>
+            <Button variant="primary" onClick={() => router.refresh()}>
               Try Again
             </Button>
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/page.tsx at line 110, replace
the use of window.location.reload() with the Next.js router's refresh or replace
method to reload the page. Import the useRouter hook from 'next/navigation',
initialize the router, and use router.refresh() or router.replace(router.asPath)
inside the onClick handler to reload the page while preserving client state
according to Next.js conventions.

@vercel vercel bot temporarily deployed to Preview – dashboard July 25, 2025 20:08 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (15)
apps/dashboard/lib/trpc/routers/deployment/getById.ts (1)

33-35: Add workspace validation to the branch query.

The branch query should verify that the fetched branch belongs to the same workspace to prevent potential data exposure.

Apply this diff to add workspace validation:

-        branch = await db.query.branches.findFirst({
-          where: (table, { eq }) => eq(table.id, branchId),
-        });
+        branch = await db.query.branches.findFirst({
+          where: (table, { eq, and }) =>
+            and(
+              eq(table.id, branchId),
+              eq(table.workspaceId, ctx.workspace.id)
+            ),
+        });
apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (2)

59-59: Use Next.js router instead of window.history.back().

Based on the codebase patterns, Next.js router should be used for client-side navigation to preserve client state and enable smoother transitions between pages.

Apply this diff to use the router:

-            <Button variant="outline" size="sm" onClick={() => window.history.back()}>
+            <Button variant="outline" size="sm" onClick={() => router.back()}>

76-295: Extract duplicate deployment selector UI into a reusable component.

The branch and deployment selector UI code is duplicated between the FROM and TO sections. This violates the DRY principle and makes maintenance harder.

Consider extracting the repeated JSX and logic into a reusable component that accepts props for selected values, change handlers, loading states, and data arrays. Replace the duplicated code with instances of this new component, passing the appropriate props for each section.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (4)

8-8: Remove commented import.

The commented import for sampleDiffData should be removed to clean up the code and avoid unnecessary clutter.

-// import { sampleDiffData } from "./constants";

93-93: Use Next.js router instead of window methods for navigation.

Replace window.history.back() with Next.js router methods for better client state preservation and smoother transitions between pages.

Apply this change:

-            <Button variant="outline" size="sm" onClick={() => window.history.back()}>
+            <Button variant="outline" size="sm" onClick={() => router.back()}>

21-647: Consider breaking down this large component for better maintainability.

This component handles multiple responsibilities and could benefit from decomposition into smaller, focused components for better testability, reusability, and maintainability.

Consider extracting:

  1. Custom hooks for data fetching logic
  2. Separate components for loading, error, and empty states
  3. Header component with navigation
  4. DeploymentInfo component for deployment details display

107-296: Extract duplicate deployment selector UI into a reusable component.

The deployment selector UI is duplicated between the "no deployment IDs" case and the success state. This violates the DRY principle and makes maintenance harder.

Create a reusable DeploymentSelector component and use it in both places to eliminate duplication.

apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1)

28-29: Commented code should be removed or documented.

These commented lines for gitCommitMessage appear in multiple places. If this field is planned for future implementation, consider adding a more descriptive comment explaining when it will be added.

-            // TODO: add this column
-            //gitCommitMessage: true,
+            // TODO: gitCommitMessage field pending database migration
+            // Tracked in issue #XXX
+            //gitCommitMessage: true,
apps/dashboard/app/(app)/projects/page.tsx (4)

21-28: Consider moving the Project interface to a shared types file

Since this interface represents a core data structure that's likely used across multiple components, it would be better to define it in a shared types file (e.g., types/project.ts) for reusability and consistency.


74-81: Extract the generateSlug function to a utility module

This slug generation logic could be useful in other parts of the application. Consider moving it to a shared utilities file.


146-148: Use proper close icon instead of text

Using "✕" as text is not ideal for accessibility and consistency.

-<Button onClick={() => setShowCreateForm(false)} variant="ghost" size="sm">
-
-</Button>
+<Button onClick={() => setShowCreateForm(false)} variant="ghost" size="sm">
+  <X className="w-4 h-4" />
+</Button>

Note: You'll need to import X from lucide-react.


235-254: Consider extracting the loading skeleton into a separate component

The loading skeleton pattern is repeated and could be extracted into a reusable component for better maintainability.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (3)

149-158: Extract color mappings to constants for better maintainability

The color utility functions contain hardcoded style strings that should be extracted to constants as previously suggested.


322-341: Hardcoded endpoint logic needs generalization

The getDiffType function contains hardcoded logic specific to the /users endpoint as previously flagged. This should be generalized for production use.


33-956: Component refactoring still needed for maintainability

As previously noted, this 900+ line component handles too many responsibilities and should be broken down into smaller, focused components for better maintainability, testability, and performance.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 32ad865 and 13216e0.

📒 Files selected for processing (7)
  • apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/page.tsx (2 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/getById.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1 hunks)
🧰 Additional context used
🧠 Learnings (8)
📓 Common learnings
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (17)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: MichaelUnkey
PR: #3425
File: apps/engineering/content/design/components/filter/control-cloud.examples.tsx:73-83
Timestamp: 2025-07-02T14:13:01.711Z
Learning: In apps/engineering/content/design/components/, when the RenderComponentWithSnippet component does not render code snippets correctly, use the customCodeSnippet prop to manually provide the correct JSX code as a string. This manual approach is necessary due to technical limitations in the automatic rendering mechanism.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: ogzhanolguncu
PR: #3311
File: apps/dashboard/components/logs/llm-search/components/search-input.tsx:14-14
Timestamp: 2025-06-10T14:21:42.413Z
Learning: In Next.js applications, importing backend modules into frontend components causes bundling issues and can expose server-side code to the client bundle. For simple constants like limits, it's acceptable to duplicate the value rather than compromise the frontend/backend architectural separation.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (14)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/trpc/routers/key/create.ts:11-14
Timestamp: 2025-06-02T11:09:58.791Z
Learning: In the unkey codebase, TypeScript and the env() function implementation already provide sufficient validation for environment variables, so additional runtime error handling for missing env vars is not needed.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.

Learnt from: chronark
PR: #2544
File: apps/api/src/pkg/env.ts:4-6
Timestamp: 2024-10-23T12:05:31.121Z
Learning: The cloudflareRatelimiter type definition in apps/api/src/pkg/env.ts should not have its interface changed; it should keep the limit method returning Promise<{ success: boolean }> without additional error properties.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: Flo4604
PR: #3421
File: go/apps/api/openapi/openapi.yaml:196-200
Timestamp: 2025-07-03T05:58:10.699Z
Learning: In the Unkey codebase, OpenAPI 3.1 is used, which allows sibling keys (such as description) alongside $ref in schema objects. Do not flag this as an error in future reviews.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

Learnt from: ogzhanolguncu
PR: #3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/deploy.go:14-14
Timestamp: 2025-07-15T14:47:43.718Z
Learning: In go/cmd/cli/commands/deploy/deploy.go, the DEBUG_DELAY constant is temporary code that ogzhanolguncu plans to remove in a future PR, rather than making it configurable.

Learnt from: chronark
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/components/log-footer.tsx:58-61
Timestamp: 2024-12-03T14:21:19.543Z
Learning: For the "Outcome" field in the LogFooter component (apps/dashboard/app/(app)/logs/components/log-details/components/log-footer.tsx), default to "N/A" instead of "VALID" when handling null values to avoid confusing customers.

apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (17)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: ogzhanolguncu
PR: #3311
File: apps/dashboard/components/logs/llm-search/components/search-input.tsx:14-14
Timestamp: 2025-06-10T14:21:42.413Z
Learning: In Next.js applications, importing backend modules into frontend components causes bundling issues and can expose server-side code to the client bundle. For simple constants like limits, it's acceptable to duplicate the value rather than compromise the frontend/backend architectural separation.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: ogzhanolguncu
PR: #2866
File: apps/dashboard/app/(app)/ratelimits/[namespaceId]/logs/components/table/log-details/components/log-footer.tsx:85-98
Timestamp: 2025-02-05T12:56:44.873Z
Learning: The RequestResponseDetails component in the ratelimit logs UI already handles empty content cases by preventing rendering when content is empty, so no additional empty state handling is needed in the parent components.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: MichaelUnkey
PR: #2810
File: internal/ui/src/components/date-time/components/time-split.tsx:10-14
Timestamp: 2025-01-22T16:51:59.978Z
Learning: The DateTime component in internal/ui/src/components/date-time/components/time-split.tsx already includes sufficient validation through handleChange and handleBlur functions, making additional runtime validation unnecessary.

Learnt from: chronark
PR: #2792
File: apps/dashboard/app/(app)/settings/user/update-user-email.tsx:76-78
Timestamp: 2025-01-07T19:55:33.055Z
Learning: In the Unkey codebase, the Empty component can be used as a container for loading states, as demonstrated in the UpdateUserEmail component where it wraps the Loading component.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

apps/dashboard/lib/trpc/routers/deployment/getById.ts (5)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.

Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using eq(table.workspaceId, ctx.workspace.id) to prevent cross-workspace access.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.

apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx (18)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/hooks/use-bookmarked-filters.ts:0-0
Timestamp: 2025-01-30T20:51:44.359Z
Learning: The user (ogzhanolguncu) prefers to handle refactoring suggestions in separate PRs to maintain focus in the current PR.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: Flo4604
PR: #3421
File: go/apps/api/openapi/openapi.yaml:196-200
Timestamp: 2025-07-03T05:58:10.699Z
Learning: In the Unkey codebase, OpenAPI 3.1 is used, which allows sibling keys (such as description) alongside $ref in schema objects. Do not flag this as an error in future reviews.

Learnt from: ogzhanolguncu
PR: #3499
File: apps/dashboard/app/new/hooks/use-workspace-step.tsx:19-26
Timestamp: 2025-07-11T13:00:05.416Z
Learning: In the Unkey codebase, ogzhanolguncu prefers to keep commented code for planned future features (like slug-based workspaces) rather than removing it, as it serves as a reference for upcoming implementation.

Learnt from: ogzhanolguncu
PR: #3564
File: go/cmd/cli/commands/deploy/flags.go:17-20
Timestamp: 2025-07-15T14:45:18.920Z
Learning: In the go/cmd/cli/commands/deploy/ directory, ogzhanolguncu prefers to keep potentially temporary features (like UNKEY_DOCKER_REGISTRY environment variable) undocumented in help text if they might be deleted in the future, to avoid documentation churn.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/trpc/routers/key/create.ts:11-14
Timestamp: 2025-06-02T11:09:58.791Z
Learning: In the unkey codebase, TypeScript and the env() function implementation already provide sufficient validation for environment variables, so additional runtime error handling for missing env vars is not needed.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

apps/dashboard/app/(app)/projects/page.tsx (19)

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.254Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx:7-43
Timestamp: 2025-05-15T16:09:49.243Z
Learning: For type safety issues involving any type assertions, the team prefers to address these systematically with linter updates rather than fixing them individually in code reviews.

Learnt from: ogzhanolguncu
PR: #3380
File: apps/dashboard/app/(app)/ratelimits/[namespaceId]/namespace-navbar.tsx:40-66
Timestamp: 2025-06-19T10:39:29.388Z
Learning: tRPC has built-in caching that prevents skeleton flashing during component re-renders and navigation, so concerns about !data || isLoading conditions causing loading state flashes are generally not needed.

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: MichaelUnkey
PR: #3439
File: apps/engineering/content/design/components/dialogs/dialog-container.mdx:103-111
Timestamp: 2025-07-08T12:47:17.841Z
Learning: In component documentation files (e.g., .mdx files in apps/engineering/content/design/components/), the Accessibility section is included in the standard template and should not be removed from component documentation.

Learnt from: chronark
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/components/log-footer.tsx:58-61
Timestamp: 2024-12-03T14:21:19.543Z
Learning: For the "Outcome" field in the LogFooter component (apps/dashboard/app/(app)/logs/components/log-details/components/log-footer.tsx), default to "N/A" instead of "VALID" when handling null values to avoid confusing customers.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (34)

Learnt from: mcstepp
PR: #3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.179Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:16-20
Timestamp: 2024-10-23T16:21:47.395Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, refactoring type definitions into an interface is not necessary at this time.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:26-37
Timestamp: 2024-10-23T16:20:19.324Z
Learning: When reviewing React components in this project, avoid suggesting manual memoization with useMemo for performance optimizations, as the team prefers to rely on the React compiler to handle such optimizations.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx:37-49
Timestamp: 2024-12-03T14:23:07.189Z
Learning: In apps/dashboard/app/(app)/logs/components/log-details/resizable-panel.tsx, the resize handler is already debounced.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/search.tsx:41-57
Timestamp: 2024-10-23T16:19:42.049Z
Learning: For the FilterableCommand component in apps/www/components/glossary/search.tsx, adding error handling and loading states to the results list is not necessary.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Use AIDEV-NOTE:, AIDEV-TODO:, AIDEV-BUSINESS_RULE:, or AIDEV-QUESTION: (all-caps prefix) as anchor comments aimed at AI and developers.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:60-78
Timestamp: 2025-06-02T11:09:05.843Z
Learning: The vault implementation in apps/dashboard/lib/vault.ts is a duplicate of the vault package from api and should be kept consistent with the original implementation.

Learnt from: ogzhanolguncu
PR: #3292
File: apps/dashboard/lib/vault.ts:80-97
Timestamp: 2025-06-02T11:08:56.397Z
Learning: The vault.ts file in apps/dashboard/lib/vault.ts is a duplicate of the vault package from the api directory and should be kept consistent with that original implementation.

Learnt from: ogzhanolguncu
PR: #2143
File: apps/dashboard/components/ui/group-button.tsx:21-31
Timestamp: 2024-12-03T14:07:45.173Z
Learning: In the ButtonGroup component (apps/dashboard/components/ui/group-button.tsx), avoid suggesting the use of role="group" in ARIA attributes.

Learnt from: MichaelUnkey
PR: #3425
File: apps/engineering/content/design/components/filter/control-cloud.examples.tsx:73-83
Timestamp: 2025-07-02T14:13:01.711Z
Learning: In apps/engineering/content/design/components/, when the RenderComponentWithSnippet component does not render code snippets correctly, use the customCodeSnippet prop to manually provide the correct JSX code as a string. This manual approach is necessary due to technical limitations in the automatic rendering mechanism.

Learnt from: ogzhanolguncu
PR: #2825
File: apps/dashboard/app/(app)/logs-v2/components/controls/components/logs-datetime/index.tsx:0-0
Timestamp: 2025-01-30T20:38:00.058Z
Learning: In the logs dashboard, keyboard shortcuts that toggle UI elements (like popovers) should be implemented in the component that owns the state being toggled, not in the presentational wrapper components. For example, the 'T' shortcut for toggling the datetime filter is implemented in DatetimePopover, not in LogsDateTime.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-stepper-mobile.tsx:16-20
Timestamp: 2024-10-23T16:25:33.113Z
Learning: In the apps/www/components/glossary/terms-stepper-mobile.tsx file, avoid suggesting to extract the term navigation logic into a custom hook, as the user prefers to keep the component straightforward.

Learnt from: p6l-richard
PR: #2085
File: apps/www/components/glossary/terms-rolodex-desktop.tsx:39-50
Timestamp: 2024-10-23T16:19:59.657Z
Learning: Debouncing of the scroll handler is not needed yet in the function TermsRolodexDesktop in apps/www/components/glossary/terms-rolodex-desktop.tsx.

Learnt from: ogzhanolguncu
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx:50-65
Timestamp: 2025-05-15T16:26:08.666Z
Learning: In the Unkey dashboard, Next.js router (router.push) should be used for client-side navigation instead of window.location.href to preserve client state and enable smoother transitions between pages.

Learnt from: unrenamed
PR: #2652
File: apps/www/components/particles.tsx:88-90
Timestamp: 2024-11-08T11:44:42.947Z
Learning: In React TypeScript components, if a function memoized with useCallback is not called elsewhere in the component, and a useEffect is used to invoke it, do not suggest removing the useEffect as it is necessary to execute the function.

Learnt from: MichaelUnkey
PR: #3439
File: apps/engineering/content/design/components/dialogs/dialog-container.mdx:103-111
Timestamp: 2025-07-08T12:47:17.841Z
Learning: In component documentation files (e.g., .mdx files in apps/engineering/content/design/components/), the Accessibility section is included in the standard template and should not be removed from component documentation.

Learnt from: chronark
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/upsert-role/components/warning-callout.tsx:22-27
Timestamp: 2025-07-03T11:57:15.263Z
Learning: The target prop on InlineLink components in the @unkey/ui package is a boolean prop, not a string. When target is truthy, it automatically sets target="_blank" and rel="noopener noreferrer" on the underlying anchor element. Using just target (equivalent to target={true}) is the correct way to make the link open in a new tab.

Learnt from: unrenamed
PR: #2652
File: apps/dashboard/components/dashboard/copy-button.tsx:38-38
Timestamp: 2024-11-08T11:40:17.737Z
Learning: The copyToClipboardWithMeta function currently has an unused _meta parameter. Consider removing it or utilizing it appropriately, especially when introducing the useCopyToClipboard hook.

Learnt from: chronark
PR: #2146
File: apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx:74-75
Timestamp: 2024-10-04T17:27:09.821Z
Learning: In apps/dashboard/app/(app)/apis/[apiId]/settings/default-prefix.tsx, the hidden <input> elements for workspaceId and keyAuthId work correctly without being registered with React Hook Form.

Learnt from: ogzhanolguncu
PR: #3528
File: apps/dashboard/app/(app)/settings/team/role-switcher.tsx:60-76
Timestamp: 2025-07-11T12:57:39.875Z
Learning: In the Select component from @unkey/ui, when fixing chevron overflow/positioning issues, applying w-fit directly to the SelectTrigger doesn't resolve the problem. A wrapper div with w-fit class around the entire Select component is required to properly contain the chevron within the trigger area.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:32-36
Timestamp: 2025-04-22T17:33:28.162Z
Learning: In the Unkey dashboard UI for delete protection, the button/link to initiate the process is labeled "Disable Delete Protection" while the confirmation button is labeled "Disable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: MichaelUnkey
PR: #3173
File: apps/docs/security/delete-protection.mdx:21-24
Timestamp: 2025-04-22T17:34:04.438Z
Learning: In the Unkey dashboard UI for enabling delete protection, the button/link to initiate the process is labeled "Enable Delete Protection" while the confirmation button is labeled "Enable API Delete Protection". The documentation should maintain these different labels to match the actual UI.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx:230-266
Timestamp: 2025-05-15T15:59:20.955Z
Learning: Avoid using any type in TypeScript code as it defeats the purpose of type safety and will cause linter issues in the future. Instead, create proper interfaces or utilize existing type definitions, especially for complex nested objects.

Learnt from: mcstepp
PR: #3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx:7-43
Timestamp: 2025-05-15T16:09:49.243Z
Learning: For type safety issues involving any type assertions, the team prefers to address these systematically with linter updates rather than fixing them individually in code reviews.

Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-21T18:05:58.236Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,env,conf,html,css,scss,xml,c,h,cpp,java,rb,rs,php,pl,sql} : Do not remove AIDEV-*s without explicit human instruction.

Learnt from: mcstepp
PR: #3254
File: apps/dashboard/components/logs/hooks/use-bookmarked-filters.ts:183-195
Timestamp: 2025-05-13T14:38:44.134Z
Learning: When working with browser storage (like localStorage) in this codebase, always implement try/catch blocks to handle Safari quirks and other browser-specific issues that users have been experiencing. Safari Private Browsing mode in particular can throw errors when accessing localStorage.

Learnt from: Srayash
PR: #2568
File: apps/dashboard/app/auth/sign-up/oauth-signup.tsx:25-25
Timestamp: 2024-10-25T23:53:41.716Z
Learning: In the React component OAuthSignUp (apps/dashboard/app/auth/sign-up/oauth-signup.tsx), adding a useEffect cleanup function to reset the isLoading state causes a "something went wrong" popup to appear before redirecting when a user clicks on signup.

Learnt from: unrenamed
PR: #2214
File: apps/www/components/ui/code-editor.tsx:26-26
Timestamp: 2024-10-05T17:36:37.293Z
Learning: Using array index as key in nested mapping is acceptable in React components when the list does not change over time (no additions, removals, or reordering).

Learnt from: unrenamed
PR: #2214
File: apps/www/components/ui/code-editor.tsx:26-26
Timestamp: 2024-10-08T15:33:04.290Z
Learning: Using array index as key in nested mapping is acceptable in React components when the list does not change over time (no additions, removals, or reordering).

Learnt from: ogzhanolguncu
PR: #2876
File: apps/dashboard/components/logs/datetime/constants.ts:96-96
Timestamp: 2025-02-06T17:41:47.228Z
Learning: In the Unkey codebase, avoid hardcoding IDs or indices that depend on array positions, as the arrays may be modified in the future. Instead, use methods like find with unique identifiers or properties to locate specific items.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-04T20:47:34.791Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:85-88
Timestamp: 2024-10-08T15:33:04.290Z
Learning: In navigation code (e.g., apps/dashboard/lib/constants/workspace-navigations.tsx), prefer using segments.at(0) over segments[0] to handle cases when the segments array might be empty.

Learnt from: ogzhanolguncu
PR: #3401
File: apps/dashboard/app/(app)/logs/filters.query-params.ts:10-0
Timestamp: 2025-06-24T13:29:10.129Z
Learning: The buildQueryParams function in apps/dashboard/app/(app)/logs/filters.query-params.ts calls useFilters() hook inside it, but this is valid because the function is only called from within other React hooks, maintaining the Rules of Hooks compliance.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Build / Build
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (10)
apps/dashboard/lib/trpc/routers/deployment/getById.ts (1)

6-63: Well-implemented deployment retrieval procedure.

The procedure correctly implements workspace scoping for the main deployment query, proper error handling, and returns a well-structured response with deployment and branch details.

apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (1)

15-276: Well-implemented comparison interface with proper state management.

The component correctly handles loading states, empty states, and conditional data fetching. The UI provides clear visual feedback and the state management logic properly synchronizes branch and deployment selections.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (1)

31-71: Well-implemented data fetching and state synchronization.

The component correctly implements conditional data fetching, properly synchronizes branch selections based on deployment details from URL parameters, and handles state management effectively.

apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (2)

17-87: Well-implemented deployment validation and workspace scoping.

The procedure correctly validates both deployments exist, belong to the current workspace, and have OpenAPI specs available. The error handling with specific TRPC error codes provides clear feedback to the client.


106-163: Appropriate fallback implementation for POC purposes.

Based on the project context as a work-in-progress demonstration, the mock data fallback provides a good user experience when the control plane is unavailable, allowing UI development and testing to continue smoothly.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (5)

1-31: Clean imports and interface definitions

The imports are well-organized and the DiffViewerProps interface is properly typed with optional deployment parameters. The ViewMode type union provides good type safety for the different view states.


64-114: Well-implemented statistics and filtering logic

The use of useMemo for expensive calculations is appropriate, and the filtering logic properly handles multiple filter types. The grouping by path and operation provides a good data structure for the UI rendering.


174-269: Hardcoded demo data is intentional for POC

Based on the retrieved learnings, these hardcoded spec functions are intentionally kept as mock data for demonstration purposes in this POC implementation, which aligns with the PR's work-in-progress status.


369-585: Well-structured view rendering with good visual hierarchy

The separate view rendering functions provide good separation of concerns. The timeline view particularly has excellent visual design with proper use of icons, color coding, and spacing to create a clear hierarchy of information.


587-956: Comprehensive UI implementation for diff visualization

The main render method implements a feature-rich interface with tabbed navigation, filtering, and detailed change visualization. While complex, it provides a complete demonstration of API diff comparison functionality with good attention to visual hierarchy and user experience.

Comment on lines +255 to +261
<a
href={`/projects/${projectId}`}
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-content-subtle hover:text-content transition-colors rounded-md hover:bg-background-subtle"
>
<ArrowLeft className="w-4 h-4" />
Back to {project.name}
</a>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Next.js navigation for client-side routing

Replace the anchor tag with Next.js navigation to preserve client state and enable smoother transitions.

-<a
-  href={`/projects/${projectId}`}
-  className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-content-subtle hover:text-content transition-colors rounded-md hover:bg-background-subtle"
->
+<button
+  onClick={() => router.push(`/projects/${projectId}`)}
+  className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-content-subtle hover:text-content transition-colors rounded-md hover:bg-background-subtle"
+>
   <ArrowLeft className="w-4 h-4" />
   Back to {project.name}
-</a>
+</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<a
href={`/projects/${projectId}`}
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-content-subtle hover:text-content transition-colors rounded-md hover:bg-background-subtle"
>
<ArrowLeft className="w-4 h-4" />
Back to {project.name}
</a>
<button
onClick={() => router.push(`/projects/${projectId}`)}
className="inline-flex items-center gap-2 px-3 py-1.5 text-sm font-medium text-content-subtle hover:text-content transition-colors rounded-md hover:bg-background-subtle"
>
<ArrowLeft className="w-4 h-4" />
Back to {project.name}
</button>
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/branches/[branchName]/page.tsx
around lines 255 to 261, replace the anchor tag used for navigation with Next.js
Link component to enable client-side routing. Import Link from 'next/link' and
wrap the content inside the Link component with the href set to
`/projects/${projectId}`. Remove the anchor tag's href attribute and className
should be moved to the child element inside Link to preserve styles and enable
smoother transitions without full page reloads.

Comment on lines +53 to +55
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(
new Set(["/users", "/users/{userId}"]),
);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Remove hardcoded default expanded paths

The default expanded paths are hardcoded to specific endpoints (/users, /users/{userId}) which appears to be demo-specific data. For production use, this should either be empty or dynamically determined from the actual diff data.

-  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(
-    new Set(["/users", "/users/{userId}"]),
-  );
+  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set());
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(
new Set(["/users", "/users/{userId}"]),
);
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set());
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx
around lines 53 to 55, the default expandedPaths state is initialized with
hardcoded demo-specific paths. Remove these hardcoded values and initialize
expandedPaths as an empty Set or derive its initial values dynamically based on
the actual diff data to make it suitable for production use.

@coderabbitai coderabbitai bot mentioned this pull request Jul 28, 2025
18 tasks
@chronark chronark self-assigned this Jul 28, 2025
Copy link
Collaborator

@chronark chronark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcstepp are we doing the schema changes separately then?

@@ -2,8 +2,8 @@

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Re: line +1]

is this still needed after we moved to /projects?

See this comment inline on Graphite.

Copy link
Collaborator

@chronark chronark left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The beta flag is not enforced anywhere, anyone can just go to /projects
doesn't that kind of defeat the purpose of the flag?

Copy link
Collaborator Author

mcstepp commented Jul 28, 2025

I only used the beta flag for the sidebar, didnt realize we wanted on the page itself, I can add that

@mcstepp mcstepp force-pushed the ENG-1902-projects-ui branch from 13216e0 to a26435d Compare July 28, 2025 20:28
@vercel vercel bot temporarily deployed to Preview – engineering July 28, 2025 20:29 Inactive
@mcstepp mcstepp force-pushed the ENG-1902-projects-ui branch from a26435d to ed57a3f Compare July 29, 2025 21:52
@vercel vercel bot temporarily deployed to Preview – engineering July 29, 2025 21:53 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard July 29, 2025 21:55 Inactive
@vercel vercel bot temporarily deployed to Preview – engineering August 1, 2025 13:32 Inactive
@vercel vercel bot temporarily deployed to Preview – dashboard August 1, 2025 13:34 Inactive
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

🔭 Outside diff range comments (11)
go/apps/ctrl/services/deployment/service.go (1)

10-20: Rename the embedded handler for clarity.

The service now lives in package deployment, yet it still embeds UnimplementedVersionServiceHandler.
If the proto definitions have already been migrated (or will be soon), switch to the corresponding DeploymentServiceHandler to avoid cognitive dissonance and future grepping mistakes.

-	ctrlv1connect.UnimplementedVersionServiceHandler
+	ctrlv1connect.UnimplementedDeploymentServiceHandler

If the proto hasn’t been renamed yet, consider adding a TODO comment explaining the temporary mismatch.

go/pkg/db/queries/deployment_step_insert.sql (1)

11-14: status not updated on upsert; created_at should stay immutable.

If the same unique key is hit later with a new status, the upsert leaves the old status intact while overwriting created_at, which should usually remain the first-insert timestamp.

Suggested fix:

 ON DUPLICATE KEY UPDATE
-    message = VALUES(message),
-    error_message = VALUES(error_message),
-    created_at = VALUES(created_at);
+    status        = VALUES(status),
+    message       = VALUES(message),
+    error_message = VALUES(error_message),
+    -- keep original creation time to preserve event ordering
+    created_at    = created_at;
go/apps/ctrl/services/deployment/deploy_workflow.go (1)

52-58: Consider updating legacy VersionID field for consistency.

The BuildInfo struct still contains a VersionID field on line 56, which seems inconsistent with the deployment-centric refactor. If this struct is still used, consider renaming it to DeploymentID for consistency.

 type BuildInfo struct {
 	BuildID     string `json:"build_id"`
 	WorkspaceID string `json:"workspace_id"`
 	ProjectID   string `json:"project_id"`
-	VersionID   string `json:"version_id"`
+	DeploymentID string `json:"deployment_id"`
 	DockerImage string `json:"docker_image"`
 }
go/cmd/deploy/main.go (3)

71-79: Fix inconsistent terminology in step sequence map

The stepSequence map contains mixed terminology with some entries still referencing "Version" while the codebase has migrated to "Deployment". This could cause step prediction to fail.

 var stepSequence = map[string]string{
-	"Version queued and ready to start":  "Downloading Docker image:",
+	"Deployment queued and ready to start":  "Downloading Docker image:",
 	"Downloading Docker image:":          "Building rootfs from Docker image:",
 	"Building rootfs from Docker image:": "Uploading rootfs image to storage",
 	"Uploading rootfs image to storage":  "Creating VM for version:",
-	"Creating VM for deployment:":        "VM booted successfully:",
+	"Creating VM for deployment:":        "VM booted successfully:",
 	"VM booted successfully:":            "Assigned hostname:",
 	"Assigned hostname:":                 MsgDeploymentStepCompleted,
 }

Also, line 75 still references "Creating VM for version:" which should be updated to "Creating VM for deployment:".


297-308: Consider renaming variable for consistency

The variable finalVersion (line 297) still uses "version" terminology while the rest of the codebase has migrated to "deployment". Consider renaming it to finalDeployment for consistency.

-	// Track final version for completion info
-	var finalVersion *ctrlv1.Version
+	// Track final deployment for completion info
+	var finalDeployment *ctrlv1.Version

Note: The proto type ctrlv1.Version and status enums like VERSION_STATUS_FAILED suggest the proto definitions haven't been updated yet. This might be intentional to avoid breaking changes, but should be tracked for future updates.


371-377: Rename function to match deployment terminology

The function handleVersionFailure should be renamed to handleDeploymentFailure to maintain consistency with the deployment terminology used throughout the codebase.

-func handleVersionFailure(controlPlane *ControlPlaneClient, version *ctrlv1.Version, ui *UI) error {
+func handleDeploymentFailure(controlPlane *ControlPlaneClient, version *ctrlv1.Version, ui *UI) error {
 	errorMsg := controlPlane.getFailureMessage(version)
 	ui.CompleteCurrentStep(MsgDeploymentFailed, false)
 	ui.PrintError(MsgDeploymentFailed)
 	ui.PrintErrorDetails(errorMsg)
 	return fmt.Errorf("deployment failed: %s", errorMsg)
 }

Also update the call site at line 303:

-			return handleVersionFailure(controlPlane, event.Version, ui)
+			return handleDeploymentFailure(controlPlane, event.Version, ui)
go/cmd/deploy/control_plane.go (2)

36-43: Document the version/deployment terminology mismatch

The control plane client still uses VersionServiceClient and version-based API methods internally while exposing deployment-based methods publicly. This creates a confusing abstraction layer.

Consider adding a comment to document this temporary mismatch:

 // ControlPlaneClient handles API operations with the control plane
 type ControlPlaneClient struct {
+	// TODO: The proto service and methods still use "Version" terminology.
+	// This will be updated when the proto definitions are migrated to "Deployment".
 	client ctrlv1connect.VersionServiceClient
 	opts   DeployOptions
 }

59-59: Track the hard-coded environment configuration

The environment ID is hard-coded to "env_prod" with a TODO comment. This should be made configurable to support different deployment environments (preview, staging, production).

Would you like me to create an issue to track making the environment ID configurable through command flags or configuration?

go/demo_api/main.go (2)

26-42: Version mismatch between implementation and OpenAPI spec

The API implementation still uses v1 endpoints (/v1/liveness, /v1/hello) while the OpenAPI specification describes v2 endpoints (/v2/health, /v2/greeting). This inconsistency will cause the API to not match its documentation.

Either update the implementation to match the v2 spec:

-	mux.HandleFunc("/v1/liveness", func(w http.ResponseWriter, r *http.Request) {
+	mux.HandleFunc("/v2/health", func(w http.ResponseWriter, r *http.Request) {
+		response := map[string]interface{}{
+			"status": "healthy",
+			"timestamp": time.Now().UTC().Format(time.RFC3339),
+		}
 		w.Header().Set("Content-Type", "application/json")
 		w.WriteHeader(http.StatusOK)
-		fmt.Fprint(w, "OK")
+		json.NewEncoder(w).Encode(response)
 	})

-	mux.HandleFunc("/v1/hello", func(w http.ResponseWriter, r *http.Request) {
+	mux.HandleFunc("/v2/greeting", func(w http.ResponseWriter, r *http.Request) {
+		name := r.URL.Query().Get("name")
+		if name == "" {
+			http.Error(w, `{"message":"Missing required parameter: name","error_code":"MISSING_PARAMETER"}`, http.StatusBadRequest)
+			return
+		}
 		response := HelloResponse{
-			Message:   "Hello from demo API",
+			Message:   fmt.Sprintf("Hello, %s!", name),
 			Timestamp: time.Now().UTC(),
 		}

Or revert the OpenAPI spec to describe the v1 implementation.


12-15: Struct definition doesn't match OpenAPI schema

The HelloResponse struct doesn't match the GreetingResponse schema defined in the OpenAPI spec. The OpenAPI schema expects fields like greeting, user_name, and api_version.

Update the struct to match the schema:

-type HelloResponse struct {
-	Message   string    `json:"message"`
-	Timestamp time.Time `json:"timestamp"`
+type GreetingResponse struct {
+	Greeting   string    `json:"greeting"`
+	UserName   string    `json:"user_name"`
+	Timestamp  time.Time `json:"timestamp"`
+	APIVersion string    `json:"api_version"`
 }
go/pkg/db/querier_generated.go (1)

12-1418: Consider the architectural impact of removing all branch-related operations.

The complete removal of branch-related database operations (FindBranchByProjectName, InsertBranch, UpsertBranch) represents a significant architectural change. While deployments now track git_branch as a field, there's no longer a separate branch entity in the database.

This suggests branch management might now rely entirely on Git integration rather than database persistence. Ensure this aligns with your product requirements and that any existing branch-dependent features have been properly migrated or deprecated.

♻️ Duplicate comments (14)
go/deploy/Dockerfile.dev (1)

132-136: Harden the entrypoint – adopt -Eeuo pipefail (already suggested earlier).

set -e alone misses failures in pipelines and undefined variables.
Previous review already recommended set -Eeuo pipefail; the change is still outstanding.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/constants.ts (1)

5-200: Fix duplicate IDs in sample data.

Several diff changes have duplicate IDs which could cause issues in UI components that expect unique identifiers:

  • "new-required-request-property" appears on lines 15 and 25
  • "request-property-min-length-increased" appears on lines 42 and 52
  • "request-parameter-type-changed" appears on lines 69, 78, and 105
  • "response-property-type-changed" appears on lines 60, 87, and 114

Make the IDs unique by adding suffixes or using more descriptive identifiers:

    {
-      id: "new-required-request-property",
+      id: "new-required-request-property-acceptTerms",
      text: "added the new required request property 'acceptTerms'",
      // ...
    },
    {
-      id: "new-required-request-property", 
+      id: "new-required-request-property-fullName",
      text: "added the new required request property 'fullName'",
      // ...
    },
apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (1)

103-174: Consider extracting the deployment selector into a reusable component.

The FROM and TO sections contain nearly identical dropdown UI code. This duplication could be reduced by creating a reusable DeploymentSelector component.

Extract the repeated JSX into a reusable component:

+interface DeploymentSelectorProps {
+  title: string;
+  label: string;
+  placeholder: string;
+  value: string;
+  onChange: (value: string) => void;
+  deployments: DeploymentData[];
+  isLoading: boolean;
+  getDeploymentLabel: (deployment: DeploymentData) => any;
+}
+
+function DeploymentSelector({ title, label, placeholder, value, onChange, deployments, isLoading, getDeploymentLabel }: DeploymentSelectorProps) {
+  // Extract the dropdown JSX here
+}

Also applies to: 189-260

apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1)

97-99: Move hardcoded control plane URL to environment variable.

The hardcoded localhost URL will not work in production environments. This is acknowledged in the TODO comment but needs immediate attention.

Apply this diff to use an environment variable:

 // Call control plane API
-// TODO: put this in env var
 let diffData:
   | {
       changes?: Array<{
         id: string;
         text: string;
         level: number;
         operation: string;
         path: string;
         source: string;
         section: string;
         comment: string;
       }>;
     }
   | undefined;

 try {
-  const response = await fetch(
-    "http://localhost:7091/ctrl.v1.OpenApiService/GetOpenApiDiff",
+  const controlPlaneUrl = process.env.CONTROL_PLANE_URL || "http://localhost:7091";
+  const response = await fetch(
+    `${controlPlaneUrl}/ctrl.v1.OpenApiService/GetOpenApiDiff`,

Also add the environment variable to your .env configuration files.

apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (1)

126-271: Extract duplicate deployment selector UI into a reusable component.

The deployment selector UI is duplicated between the "From" and "To" sections. This violates the DRY principle and makes maintenance harder.

Create a new component DeploymentSelector:

interface DeploymentSelectorProps {
  title: string;
  subtitle: string;
  colorScheme: 'green' | 'blue';
  selectedDeployment: string;
  onDeploymentChange: (value: string) => void;
  deployments: DeploymentData[];
  deploymentsLoading: boolean;
  placeholderText: string;
}

function DeploymentSelector({
  title,
  subtitle,
  colorScheme,
  selectedDeployment,
  onDeploymentChange,
  deployments,
  deploymentsLoading,
  placeholderText
}: DeploymentSelectorProps) {
  const bgColor = colorScheme === 'green' ? 'bg-green-50' : 'bg-blue-50';
  const borderColor = colorScheme === 'green' ? 'border-green-200' : 'border-blue-200';
  const titleColor = colorScheme === 'green' ? 'text-green-800' : 'text-blue-800';
  
  return (
    <div className="space-y-4">
      <div className={`${bgColor} border ${borderColor} rounded-lg p-4`}>
        <h3 className={`text-lg font-medium ${titleColor} mb-4`}>{title}</h3>
        {/* Move the Select component and its contents here */}
      </div>
    </div>
  );
}

Then use it in both places:

-{/* From Selection */}
-<div className="space-y-4">
-  {/* ... entire From selector code ... */}
-</div>
+<DeploymentSelector
+  title="From (Baseline)"
+  subtitle="Select baseline deployment"
+  colorScheme="green"
+  selectedDeployment={selectedFromDeployment}
+  onDeploymentChange={setSelectedFromDeployment}
+  deployments={sortedDeployments}
+  deploymentsLoading={deploymentsLoading}
+  placeholderText="Select baseline deployment"
+/>
apps/dashboard/app/(app)/projects/[projectId]/page.tsx (2)

88-88: Use Next.js router instead of window.location.reload().

Use Next.js router for navigation instead of window.location.reload() to preserve client state.

+import { useRouter } from "next/navigation";

export default function ProjectDetailPage(): JSX.Element {
+ const router = useRouter();
  // ... existing code ...

-            <Button variant="primary" onClick={() => window.location.reload()}>
+            <Button variant="primary" onClick={() => router.refresh()}>
               Try Again
             </Button>

49-55: Use Next.js Link component for internal navigation.

Replace anchor tags with Next.js Link component for all internal navigation to preserve client state and enable smoother transitions.

+import Link from "next/link";

// Replace all internal navigation anchor tags:
-          <a
-            href="/projects"
-            className="inline-flex items-center gap-2 px-4 py-2 border border-border rounded-lg text-content hover:bg-background-subtle transition-colors"
-          >
-            <ArrowLeft className="w-4 h-4" />
-            Back to Projects
-          </a>
+          <Link
+            href="/projects"
+            className="inline-flex items-center gap-2 px-4 py-2 border border-border rounded-lg text-content hover:bg-background-subtle transition-colors"
+          >
+            <ArrowLeft className="w-4 h-4" />
+            Back to Projects
+          </Link>

Apply similar changes to all other anchor tags for:

  • Back to Projects links
  • Deployment detail links
  • Quick action buttons (deploy, logs, diff, settings)

Also applies to: 81-87, 104-110, 149-155, 285-310, 322-349, 431-431

apps/dashboard/app/(app)/projects/page.tsx (1)

22-22: Enhance the loading state UI.

Consider using a more polished loading state that matches your design system instead of plain text.

-    <Suspense fallback={<div>Loading...</div>}>
+    <Suspense fallback={
+      <div className="flex items-center justify-center min-h-screen">
+        <div className="text-center">
+          <div className="animate-pulse">
+            <div className="h-8 bg-background-subtle rounded w-48 mb-4"></div>
+            <div className="h-4 bg-background-subtle rounded w-32"></div>
+          </div>
+        </div>
+      </div>
+    }>
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx (6)

33-37: Consider error boundary for production readiness

For production use, consider wrapping this component with an error boundary to gracefully handle any runtime errors during complex diff rendering operations.

This will prevent the entire page from crashing if there are issues with diff data structure or rendering logic.


149-158: Extract color mappings to constants for better maintainability

The inline color mappings in getOperationColor and similar functions make the code harder to maintain. Consider extracting these to constants.

const OPERATION_COLORS = {
  GET: "bg-gray-100 text-gray-800 border border-gray-200",
  POST: "bg-brand text-brand-foreground border border-gray-200",
  PUT: "bg-amber-2 text-amber-11 border border-gray-200",
  PATCH: "bg-gray-200 text-gray-700 border border-gray-300",
  DELETE: "bg-red-2 text-red-11 border border-gray-200",
  DEFAULT: "bg-gray-100 text-gray-800 border border-gray-200"
} as const;

const getOperationColor = (operation: string) => {
  return OPERATION_COLORS[operation as keyof typeof OPERATION_COLORS] || OPERATION_COLORS.DEFAULT;
};

53-55: Remove hardcoded demo-specific default expanded paths

The default expanded paths are hardcoded to specific demo endpoints (/users, /users/{userId}). For production use, this should be empty or dynamically determined.

-  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(
-    new Set(["/users", "/users/{userId}"]),
-  );
+  const [expandedPaths, setExpandedPaths] = useState<Set<string>>(new Set());

25-47: Type inconsistency between interface and runtime checks

The diffData prop is typed as required in DiffViewerProps but the component performs a falsy check on line 39. This creates a type mismatch.

Either make the prop optional in the interface or remove the falsy check:

 interface DiffViewerProps {
-  diffData: DiffData;
+  diffData?: DiffData;
   fromDeployment?: string;
   toDeployment?: string;
 }

Or if diffData is guaranteed to be provided, remove the falsy check on lines 39-47.


394-400: Add error handling for clipboard API

The clipboard API usage should include error handling for better user experience.

                 if (navigator.clipboard) {
-                  navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`);
+                  navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`)
+                    .catch((err) => {
+                      console.error('Failed to copy to clipboard:', err);
+                      // Consider showing a toast notification to user
+                    });
                 }

174-269: Add TODO comment for demo data replacement

Understanding this is POC code where mock data is acceptable, consider adding a TODO comment to indicate these functions should be replaced when moving beyond the POC phase.

   // Code diff helpers
+  // TODO: Replace hardcoded demo data with actual API spec fetching in production
   const getBeforeSpec = (path: string, operation: string) => {

Comment on lines +33 to +956
export const DiffViewer: React.FC<DiffViewerProps> = ({
diffData,
fromDeployment = "v1",
toDeployment = "v2",
}) => {
// Early return if no diffData
if (!diffData) {
return (
<div className="p-8 text-center">
<AlertTriangle className="w-12 h-12 text-warn mx-auto mb-4" />
<h3 className="text-lg font-semibold text-content mb-2">No Diff Data Available</h3>
<p className="text-content-subtle">Unable to load the comparison data.</p>
</div>
);
}

const [viewMode, setViewMode] = useState<ViewMode>("changes");
const [selectedChange, setSelectedChange] = useState<string | null>(null);
const [selectedPath, setSelectedPath] = useState<string | null>(null);
const [selectedOperation, setSelectedOperation] = useState<string | null>(null);
const [expandedPaths, setExpandedPaths] = useState<Set<string>>(
new Set(["/users", "/users/{userId}"]),
);
const [filters, setFilters] = useState({
level: null as number | null,
operation: "all",
searchQuery: "",
});
const [showFilters, setShowFilters] = useState(false);

// Statistics
const stats = useMemo(() => {
const changes = diffData?.changes || [];
const breaking = changes.filter((c) => c.level === 3).length;
const warning = changes.filter((c) => c.level === 2).length;
const info = changes.filter((c) => c.level === 1).length;
const total = changes.length;

const operations = [...new Set(changes.map((c) => c.operation))];
const paths = [...new Set(changes.map((c) => c.path))];

return { breaking, warning, info, total, operations, paths };
}, [diffData?.changes]);

// Filter changes
const filteredChanges = useMemo(() => {
const changes = diffData?.changes || [];
return changes.filter((change) => {
if (filters.level !== null && change.level !== filters.level) {
return false;
}
if (filters.operation !== "all" && change.operation !== filters.operation) {
return false;
}
if (filters.searchQuery) {
const query = filters.searchQuery.toLowerCase();
return (
change.text.toLowerCase().includes(query) ||
change.path.toLowerCase().includes(query) ||
change.id.toLowerCase().includes(query)
);
}
return true;
});
}, [diffData?.changes, filters]);

// Group changes by path and operation
const groupedChanges = useMemo(() => {
const grouped: Record<string, Record<string, DiffChange[]>> = {};

filteredChanges.forEach((change) => {
if (!grouped[change.path]) {
grouped[change.path] = {};
}
if (!grouped[change.path][change.operation]) {
grouped[change.path][change.operation] = [];
}
grouped[change.path][change.operation].push(change);
});

return grouped;
}, [filteredChanges]);

// Helper functions
const togglePathExpansion = (path: string) => {
const newExpanded = new Set(expandedPaths);
if (newExpanded.has(path)) {
newExpanded.delete(path);
} else {
newExpanded.add(path);
}
setExpandedPaths(newExpanded);
};

const getSeverityIcon = (level: number) => {
switch (level) {
case 3:
return <AlertTriangle className="w-4 h-4 text-alert" />;
case 2:
return <Info className="w-4 h-4 text-warn" />;
default:
return <Info className="w-4 h-4 text-brand" />;
}
};

const getSeverityColor = (level: number) => {
switch (level) {
case 3:
return "border-l-4 border-l-alert bg-red-2 hover:bg-red-3";
case 2:
return "border-l-4 border-l-warn bg-amber-2 hover:bg-amber-3";
default:
return "border-l-4 border-l-brand bg-background-subtle hover:bg-gray-100";
}
};

const getOperationColor = (operation: string) => {
const colors: Record<string, string> = {
GET: "bg-gray-100 text-gray-800 border border-gray-200",
POST: "bg-brand text-brand-foreground border border-gray-200",
PUT: "bg-amber-2 text-amber-11 border border-gray-200",
PATCH: "bg-gray-200 text-gray-700 border border-gray-300",
DELETE: "bg-red-2 text-red-11 border border-gray-200",
};
return colors[operation] || "bg-gray-100 text-gray-800 border border-gray-200";
};

const getChangeIcon = (changeId: string) => {
if (changeId.includes("added") || changeId.includes("new")) {
return <Plus className="w-4 h-4 text-gray-600" />;
}
if (changeId.includes("removed") || changeId.includes("deleted")) {
return <Minus className="w-4 h-4 text-alert" />;
}
if (changeId.includes("changed") || changeId.includes("modified")) {
return <Edit className="w-4 h-4 text-brand" />;
}
return <Info className="w-4 h-4 text-gray-600" />;
};

// Code diff helpers
const getBeforeSpec = (path: string, operation: string) => {
if (path === "/users" && operation === "GET") {
return `{
"get": {
"summary": "Get all users",
"parameters": [
{
"name": "limit",
"in": "query",
"schema": {
"type": "integer",
"default": 20
}
},
{
"name": "offset",
"in": "query",
"schema": {
"type": "integer",
"default": 0
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"users": { "type": "array" },
"total": { "type": "integer" },
"limit": { "type": "integer" },
"offset": { "type": "integer" }
}
}
}
}
}
}
}
}`;
}
return '{\n "endpoint": "not found"\n}';
};

const getAfterSpec = (path: string, operation: string) => {
if (path === "/users" && operation === "GET") {
return `{
"get": {
"summary": "Get all users",
"parameters": [
{
"name": "pageSize",
"in": "query",
"schema": {
"type": "integer",
"default": 10
}
},
{
"name": "page",
"in": "query",
"schema": {
"type": "integer",
"default": 1
}
},
{
"name": "status",
"in": "query",
"required": true,
"schema": {
"type": "string",
"enum": ["active", "inactive", "suspended"]
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"data": { "type": "array" },
"pagination": { "type": "object" }
}
}
}
}
}
}
}
}`;
}
return '{\n "endpoint": "not found"\n}';
};

const renderCodeLine = (
lineContent: string,
lineNumber: number,
type: "none" | "added" | "removed" | "modified" = "none",
) => {
const getLineClasses = () => {
switch (type) {
case "added":
return "bg-gray-50 border-l-4 border-l-gray-400";
case "removed":
return "bg-red-2 border-l-4 border-l-alert";
case "modified":
return "bg-amber-2 border-l-4 border-l-warn";
default:
return "bg-background";
}
};

const getLineIcon = () => {
switch (type) {
case "added":
return <Plus className="w-3 h-3 text-gray-600" />;
case "removed":
return <Minus className="w-3 h-3 text-alert" />;
case "modified":
return <Edit className="w-3 h-3 text-warn" />;
default:
return null;
}
};

return (
<div className={`flex items-start font-mono text-sm ${getLineClasses()}`}>
<div className="w-8 text-content-subtle text-right pr-2 py-1 select-none">{lineNumber}</div>
<div className="flex items-center w-6 justify-center py-1">{getLineIcon()}</div>
<div className="flex-1 py-1 pr-4">
<code className="whitespace-pre text-content">{lineContent}</code>
</div>
</div>
);
};

const renderCodeDiff = (
beforeCode: string,
afterCode: string,
path: string,
operation: string,
) => {
const beforeLines = beforeCode.split("\n");
const afterLines = afterCode.split("\n");

const getDiffType = (_lineIndex: number, side: "before" | "after", line: string) => {
if (path === "/users" && operation === "GET") {
if (side === "before") {
if (line.includes('"limit"') || line.includes('"offset"')) {
return "removed";
}
if (line.includes('"users"') || line.includes('"total"')) {
return "removed";
}
} else {
if (line.includes('"pageSize"') || line.includes('"page"') || line.includes('"status"')) {
return "added";
}
if (line.includes('"data"') || line.includes('"pagination"')) {
return "added";
}
}
}
return "none";
};

return (
<div className="grid grid-cols-2 gap-0 border border-gray-200 rounded-lg overflow-hidden">
<div className="border-r border-gray-200">
<div className="bg-gray-50 px-4 py-3 text-sm font-medium text-content border-b border-gray-200">
Before ({fromDeployment})
</div>
<div className="bg-background max-h-96 overflow-y-auto">
{beforeLines.map((line, index) =>
renderCodeLine(line, index + 1, getDiffType(index, "before", line)),
)}
</div>
</div>
<div>
<div className="bg-gray-50 px-4 py-3 text-sm font-medium text-content border-b border-gray-200">
After ({toDeployment})
</div>
<div className="bg-background max-h-96 overflow-y-auto">
{afterLines.map((line, index) =>
renderCodeLine(line, index + 1, getDiffType(index, "after", line)),
)}
</div>
</div>
</div>
);
};

// View components
const renderSideBySideView = () => (
<div className="bg-white rounded-lg border border-gray-200 h-[calc(100vh-300px)] flex flex-col">
<div className="flex items-center justify-between p-4 border-b border-gray-100 flex-shrink-0">
<div className="flex items-center space-x-4">
<h3 className="text-lg font-semibold text-content">Code Diff Comparison</h3>
{selectedPath && selectedOperation && (
<div className="flex items-center space-x-2 text-sm text-content-subtle">
<span
className={`px-2 py-1 rounded text-xs font-medium ${getOperationColor(selectedOperation)}`}
>
{selectedOperation}
</span>
<code className="bg-background-subtle px-2 py-1 rounded font-mono">
{selectedPath}
</code>
</div>
)}
</div>
<div className="flex items-center space-x-2">
<button
type="button"
className="p-2 text-content-subtle hover:text-content transition-colors"
title="Copy diff to clipboard"
onClick={() => {
if (typeof window !== "undefined" && selectedPath && selectedOperation) {
const beforeCode = getBeforeSpec(selectedPath, selectedOperation);
const afterCode = getAfterSpec(selectedPath, selectedOperation);
if (navigator.clipboard) {
navigator.clipboard.writeText(`BEFORE:\n${beforeCode}\n\nAFTER:\n${afterCode}`);
}
}
}}
>
<Copy className="w-4 h-4" />
</button>
</div>
</div>

<div className="flex-1 overflow-y-auto">
{selectedPath && selectedOperation ? (
<div className="p-6">
<div className="mb-4 p-3 bg-background-subtle rounded-lg border border-border">
<div className="flex items-center space-x-2">
<FileText className="w-4 h-4 text-content-subtle" />
<span className="font-mono text-sm text-content">{selectedPath}</span>
<span
className={`px-2 py-1 rounded text-xs font-medium ${getOperationColor(selectedOperation)}`}
>
{selectedOperation}
</span>
</div>
</div>

{renderCodeDiff(
getBeforeSpec(selectedPath, selectedOperation),
getAfterSpec(selectedPath, selectedOperation),
selectedPath,
selectedOperation,
)}

<div className="mt-4 p-3 bg-background-subtle rounded-lg">
<h4 className="text-sm font-medium text-content mb-2">Legend:</h4>
<div className="flex flex-wrap gap-4 text-xs">
<div className="flex items-center space-x-1">
<Plus className="w-3 h-3 text-gray-600" />
<span className="bg-gray-50 px-2 py-1 rounded border-l-4 border-l-gray-400">
Added lines
</span>
</div>
<div className="flex items-center space-x-1">
<Minus className="w-3 h-3 text-alert" />
<span className="bg-red-2 px-2 py-1 rounded border-l-4 border-l-alert">
Removed lines
</span>
</div>
<div className="flex items-center space-x-1">
<Edit className="w-3 h-3 text-warn" />
<span className="bg-amber-2 px-2 py-1 rounded border-l-4 border-l-warn">
Modified lines
</span>
</div>
</div>
</div>

<div className="mt-4 p-3 bg-background-subtle rounded-lg border-l-4 border-l-brand">
<h4 className="text-sm font-medium text-content mb-2">Changes for this endpoint:</h4>
<div className="space-y-1 text-sm">
{(diffData?.changes || [])
.filter((c) => c.path === selectedPath && c.operation === selectedOperation)
.map((change, _index) => (
<div
key={`${change.id}-${change.path}-${change.operation}`}
className="flex items-start space-x-2"
>
{getSeverityIcon(change.level)}
<span className="text-content">{change.text}</span>
</div>
))}
</div>
</div>
</div>
) : (
<div className="text-center py-12">
<Layers className="w-12 h-12 text-content-subtle mx-auto mb-4" />
<h3 className="text-lg font-medium text-content mb-2">Code Diff Comparison</h3>
<p className="text-content-subtle mb-4">
Select a specific endpoint from the Changes view to see the actual code differences.
</p>
<div className="flex flex-col sm:flex-row gap-2 justify-center">
<button
type="button"
onClick={() => {
setSelectedPath("/users");
setSelectedOperation("GET");
}}
className="px-4 py-2 bg-brand text-brand-foreground rounded-md hover:bg-brand/90 transition-colors"
>
View GET /users Diff
</button>
</div>
</div>
)}
</div>
</div>
);

const renderTimelineView = () => (
<div className="bg-white rounded-lg border border-gray-200 h-[calc(100vh-300px)] flex flex-col">
<div className="p-6 border-b border-gray-100 flex-shrink-0">
<div className="flex items-center space-x-3">
<Clock className="w-5 h-5 text-brand" />
<h2 className="text-lg font-semibold text-content">Change Timeline</h2>
<span className="text-sm text-content-subtle">({filteredChanges.length} changes)</span>
</div>
</div>

<div className="flex-1 overflow-y-auto">
<div className="p-6 space-y-6">
{filteredChanges.map((change, index) => (
<div key={`${change.id}-${index}`} className="relative">
{index < filteredChanges.length - 1 && (
<div className="absolute left-4 top-8 w-0.5 h-16 bg-border" />
)}
<div className="flex items-start space-x-4">
<div
className={`w-8 h-8 rounded-full flex items-center justify-center flex-shrink-0 ${
change.level === 3
? "bg-red-2"
: change.level === 2
? "bg-amber-2"
: "bg-background-subtle"
}`}
>
{getSeverityIcon(change.level)}
</div>
<div className="flex-1 bg-background-subtle rounded-lg p-4 hover:bg-gray-100 transition-colors cursor-pointer">
<div className="flex items-center space-x-2 mb-2">
<span
className={`px-2 py-1 rounded text-xs font-medium border ${getOperationColor(change.operation)}`}
>
{change.operation}
</span>
<code className="bg-gray-200 px-2 py-1 rounded text-sm font-mono">
{change.path}
</code>
<span
className={`px-2 py-1 rounded text-xs ${
change.level === 3
? "bg-red-2 text-red-11"
: change.level === 2
? "bg-amber-2 text-amber-11"
: "bg-background-subtle text-content"
}`}
>
{change.level === 3 ? "Breaking" : change.level === 2 ? "Warning" : "Info"}
</span>
{getChangeIcon(change.id)}
</div>
<p className="text-content text-sm mb-2">{change.text}</p>
{change.comment && (
<p className="text-xs text-content-subtle italic bg-background p-2 rounded">
{change.comment}
</p>
)}
<div className="flex items-center justify-between mt-3">
<div className="text-xs text-content-subtle">
ID: {change.id} • Section: {change.section}
</div>
<button
type="button"
onClick={() => {
setSelectedPath(change.path);
setSelectedOperation(change.operation);
setViewMode("side-by-side");
}}
className="text-xs text-brand hover:text-brand/80 flex items-center space-x-1 cursor-pointer bg-transparent border-none p-0"
>
<ExternalLink className="w-3 h-3" />
<span>View Details</span>
</button>
</div>
</div>
</div>
</div>
))}

{filteredChanges.length === 0 && (
<div className="text-center py-12">
<Clock className="w-12 h-12 text-content-subtle mx-auto mb-4" />
<p className="text-content-subtle">No changes match the current filters</p>
</div>
)}
</div>
</div>
</div>
);

return (
<div className="bg-background">
{/* View Mode Tabs */}
<div className="bg-white border-b border-gray-100 sticky top-0 z-10">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="py-3">
<nav className="flex space-x-6">
<button
type="button"
onClick={() => setViewMode("changes")}
className={`py-3 px-4 font-medium text-sm transition-colors border-b-2 ${
viewMode === "changes"
? "border-brand text-brand"
: "border-transparent text-content-subtle hover:text-content hover:border-gray-200"
}`}
>
<div className="flex items-center space-x-2">
<FileText className="w-4 h-4" />
<span>Changes ({stats.total})</span>
</div>
</button>
<button
type="button"
onClick={() => setViewMode("side-by-side")}
className={`py-3 px-4 font-medium text-sm transition-colors border-b-2 ${
viewMode === "side-by-side"
? "border-brand text-brand"
: "border-transparent text-content-subtle hover:text-content hover:border-gray-200"
}`}
>
<div className="flex items-center space-x-2">
<Layers className="w-4 h-4" />
<span>Side-by-Side</span>
</div>
</button>
<button
type="button"
onClick={() => setViewMode("timeline")}
className={`py-3 px-4 font-medium text-sm transition-colors border-b-2 ${
viewMode === "timeline"
? "border-brand text-brand"
: "border-transparent text-content-subtle hover:text-content hover:border-gray-200"
}`}
>
<div className="flex items-center space-x-2">
<Clock className="w-4 h-4" />
<span>Timeline</span>
</div>
</button>
</nav>
</div>
</div>
</div>

<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6">
{viewMode === "changes" && (
<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
{/* Summary Panel */}
<div className="lg:col-span-1">
<div className="bg-white rounded-lg border border-gray-200 p-6 sticky top">
<h2 className="text-lg font-semibold text-content mb-4 flex items-center">
<BarChart3 className="w-5 h-5 mr-2" />
Change Summary
</h2>

<div className="space-y-3 mb-6">
<div className="flex items-center justify-between p-2 bg-background-subtle rounded">
<span className="text-sm text-content-subtle">Total Changes</span>
<span className="font-semibold text-lg text-content">{stats.total}</span>
</div>
<div className="flex items-center justify-between p-2 bg-red-2 rounded">
<span className="text-sm text-alert flex items-center">
<AlertTriangle className="w-4 h-4 mr-1" />
Breaking
</span>
<span className="font-semibold text-alert text-lg">{stats.breaking}</span>
</div>
<div className="flex items-center justify-between p-2 bg-amber-2 rounded">
<span className="text-sm text-warn flex items-center">
<Info className="w-4 h-4 mr-1" />
Warning
</span>
<span className="font-semibold text-warn text-lg">{stats.warning}</span>
</div>
<div className="flex items-center justify-between p-2 bg-background-subtle rounded">
<span className="text-sm text-brand">Endpoints Affected</span>
<span className="font-semibold text-brand text-lg">{stats.paths.length}</span>
</div>
</div>

<button
type="button"
onClick={() => setShowFilters(!showFilters)}
className="w-full flex items-center justify-between p-3 text-sm font-medium text-content bg-background-subtle rounded-lg hover:bg-gray-100 mb-4 transition-colors"
>
<div className="flex items-center space-x-2">
<Filter className="w-4 h-4" />
<span>Filters</span>
</div>
{showFilters ? (
<ChevronDown className="w-4 h-4" />
) : (
<ChevronRight className="w-4 h-4" />
)}
</button>

{showFilters && (
<div className="space-y-4">
<div>
<label
htmlFor="search-changes"
className="block text-sm font-medium text-content mb-2"
>
Search Changes
</label>
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-4 h-4 text-content-subtle" />
<input
id="search-changes"
type="text"
value={filters.searchQuery}
onChange={(e) =>
setFilters((prev) => ({ ...prev, searchQuery: e.target.value }))
}
placeholder="Search changes..."
className="w-full pl-10 pr-4 py-2 border border-border rounded-md text-sm bg-background text-content focus:ring-2 focus:ring-brand focus:border-brand"
/>
{filters.searchQuery && (
<button
type="button"
onClick={() => setFilters((prev) => ({ ...prev, searchQuery: "" }))}
className="absolute right-3 top-1/2 transform -translate-y-1/2 hover:bg-background-subtle rounded p-1"
>
<X className="w-4 h-4 text-content-subtle hover:text-content" />
</button>
)}
</div>
</div>

<div>
<label
htmlFor="severity-level"
className="block text-sm font-medium text-content mb-2"
>
Severity Level
</label>
<select
id="severity-level"
value={filters.level || ""}
onChange={(e) =>
setFilters((prev) => ({
...prev,
level: e.target.value ? Number(e.target.value) : null,
}))
}
className="w-full border border-border rounded-md px-3 py-2 text-sm bg-background text-content focus:ring-2 focus:ring-brand focus:border-brand"
>
<option value="">All Levels</option>
<option value="3">Breaking Changes Only</option>
<option value="2">Warnings Only</option>
<option value="1">Info Only</option>
</select>
</div>

<div>
<label
htmlFor="http-method"
className="block text-sm font-medium text-content mb-2"
>
HTTP Method
</label>
<select
id="http-method"
value={filters.operation}
onChange={(e) =>
setFilters((prev) => ({ ...prev, operation: e.target.value }))
}
className="w-full border border-border rounded-md px-3 py-2 text-sm bg-background text-content focus:ring-2 focus:ring-brand focus:border-brand"
>
<option value="all">All Methods</option>
{stats.operations.map((op) => (
<option key={op} value={op}>
{op}
</option>
))}
</select>
</div>

{(filters.level !== null ||
filters.operation !== "all" ||
filters.searchQuery) && (
<button
type="button"
onClick={() =>
setFilters({ level: null, operation: "all", searchQuery: "" })
}
className="w-full px-3 py-2 text-sm text-content-subtle border border-border rounded-md hover:bg-background-subtle transition-colors"
>
Clear All Filters
</button>
)}
</div>
)}
</div>
</div>

{/* Changes List */}
<div className="lg:col-span-3">
<div className="bg-white rounded-lg border border-gray-200 h-[calc(100vh-300px)] flex flex-col">
<div className="flex-1 overflow-y-auto p-6">
{filteredChanges.length === 0 ? (
<div className="text-center py-12">
<FileText className="w-12 h-12 text-content-subtle mx-auto mb-4" />
<p className="text-content-subtle text-lg">
No changes match the current filters
</p>
<button
type="button"
onClick={() =>
setFilters({ level: null, operation: "all", searchQuery: "" })
}
className="mt-4 px-4 py-2 bg-brand text-brand-foreground rounded-md hover:bg-brand/90 transition-colors"
>
Clear Filters
</button>
</div>
) : (
<div className="space-y-4">
{Object.entries(groupedChanges).map(([path, operations]) => (
<div
key={path}
className="border border-gray-200 rounded-lg overflow-hidden"
>
<button
type="button"
onClick={() => togglePathExpansion(path)}
className="w-full flex items-center justify-between p-4 text-left hover:bg-background-subtle transition-colors"
>
<div className="flex items-center space-x-3">
{expandedPaths.has(path) ? (
<ChevronDown className="w-4 h-4 text-content-subtle" />
) : (
<ChevronRight className="w-4 h-4 text-content-subtle" />
)}
<code className="font-mono text-sm bg-background-subtle px-3 py-1 rounded-md text-content">
{path}
</code>
<span className="text-sm text-content-subtle bg-background-subtle px-2 py-1 rounded-full">
{Object.values(operations).flat().length} changes
</span>
</div>
<div className="flex items-center space-x-2">
{Object.keys(operations).map((op) => (
<span
key={op}
className={`px-2 py-1 rounded text-xs font-medium ${getOperationColor(op)}`}
>
{op}
</span>
))}
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setSelectedPath(path);
setSelectedOperation(Object.keys(operations)[0]);
setViewMode("side-by-side");
}}
className="p-1 text-content-subtle hover:text-brand transition-colors cursor-pointer"
title="View side-by-side comparison"
>
<Layers className="w-4 h-4" />
</button>
</div>
</button>

{expandedPaths.has(path) && (
<div className="border-t border-border bg-background-subtle">
{Object.entries(operations).map(([operation, changes]) => (
<div
key={operation}
className="p-4 border-b border-gray-100 last:border-b-0"
>
<div className="flex items-center space-x-2 mb-3">
<span
className={`px-3 py-1 rounded text-xs font-medium ${getOperationColor(operation)}`}
>
{operation}
</span>
<span className="text-sm text-content-subtle bg-background px-2 py-1 rounded">
{changes.length} changes
</span>
</div>

<div className="space-y-3">
{changes.map((change, index) => (
<button
type="button"
key={`${change.path}-${change.operation}-${change.id}-${index}`}
className={`p-3 rounded-r-lg cursor-pointer transition-all duration-200 text-left w-full ${getSeverityColor(
change.level,
)} ${selectedChange === `${change.id}-${index}` ? "ring-2 ring-brand shadow-md" : ""}`}
onClick={() =>
setSelectedChange(
selectedChange === `${change.id}-${index}`
? null
: `${change.id}-${index}`,
)
}
>
<div className="flex items-start space-x-3">
<div className="flex items-center space-x-2 flex-shrink-0">
{getSeverityIcon(change.level)}
{getChangeIcon(change.id)}
</div>
<div className="flex-1 min-w-0">
<p className="text-sm text-content font-medium mb-1">
{change.text}
</p>
{change.comment && (
<p className="text-xs text-content-subtle italic bg-background/70 p-2 rounded border-l-2 border-gray-300">
💡 {change.comment}
</p>
)}
<div className="flex items-center justify-between mt-3">
<div className="flex items-center space-x-4 text-xs text-content-subtle">
<span className="font-mono bg-gray-200 px-2 py-1 rounded">
ID: {change.id}
</span>
<span>Section: {change.section}</span>
</div>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
setSelectedPath(change.path);
setSelectedOperation(change.operation);
setViewMode("side-by-side");
}}
className="text-xs text-brand hover:text-brand/80 flex items-center space-x-1 bg-background px-2 py-1 rounded border hover:shadow-sm transition-all cursor-pointer"
>
<ExternalLink className="w-3 h-3" />
<span>Compare</span>
</button>
</div>
</div>
</div>
</button>
))}
</div>
</div>
))}
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
</div>
</div>
)}

{viewMode === "side-by-side" && renderSideBySideView()}
{viewMode === "timeline" && renderTimelineView()}
</div>
</div>
);
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

🛠️ Refactor suggestion

Component size is acceptable for POC but plan for refactoring

While this component is quite large (900+ lines) and handles multiple responsibilities, this is understandable for a POC/demonstration phase. When moving beyond POC, consider breaking it down into smaller, focused components:

  • DiffSummaryPanel - Statistics and filters
  • DiffChangesList - Changes list view
  • DiffSideBySideView - Code comparison view
  • DiffTimelineView - Timeline view
  • Custom hooks for state management

Would you like me to help create a refactoring plan when you're ready to move beyond the POC phase?

🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx
around lines 33 to 956, the DiffViewer component is very large and handles
multiple responsibilities, making it hard to maintain. To fix this, refactor by
splitting the component into smaller focused components: DiffSummaryPanel for
statistics and filters, DiffChangesList for the changes list view,
DiffSideBySideView for the code comparison view, and DiffTimelineView for the
timeline view. Additionally, extract state management logic into custom hooks to
improve readability and reusability.

Comment on lines +1 to +631
# CLAUDE.md





This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.





## Commands





### Development Setup



```bash



# Install dependencies



pnpm install





# Start local development environment (includes databases)



make up





# Run development servers



pnpm dev



```





### Build and Testing



```bash



# Build all packages



pnpm build





# Format and lint code



pnpm fmt





# Run tests (with concurrency control)



pnpm test





# Run integration tests (requires local environment)



make integration





# Type checking



pnpm typecheck # Check individual app package.json for specific commands



```





### Database Operations



```bash



# Run database migrations



pnpm migrate





# Generate database schema changes



make generate-sql





# ClickHouse migrations



make migrate-clickhouse



make migrate-clickhouse-reset # Reset ClickHouse schema



```





### Go-Specific Commands



```bash



# Go services (in /go directory)



go test ./...



go build ./cmd/...





# Deploy services (in /go/deploy directory)



make build # Test binary builds



make install # Build and install with systemd units



```





## Architecture Overview





Unkey is a monorepo containing both TypeScript/Node.js and Go services for API key management, authentication, and distributed rate limiting.





### Core Applications





**Dashboard** (`apps/dashboard/`)



- Next.js web interface for API key management



- Built with React, TailwindCSS, and tRPC



- Authentication via WorkOS





**API** (`apps/api/`)



- Cloudflare Workers-based API for key verification



- Uses Hono framework with OpenAPI validation



- Handles key CRUD operations and rate limiting





**Agent** (`apps/agent/`)



- Go-based distributed rate limiting service



- Uses Serf for clustering and gossip protocol



- Implements sliding window rate limiting algorithm





**Go Services** (`go/`)



- **API**: Main HTTP API server (port 7070)



- **Ctrl**: Control plane for infrastructure management (port 8080)



- **Deploy services**: VM lifecycle management (metald, builderd, etc.)





### Database Architecture





**MySQL/PlanetScale**: Primary relational data



- Tables: workspaces, apis, keys, permissions, roles, identities



- Drizzle ORM with type safety



- Read replica support





**ClickHouse**: Analytics and time-series data



- Verification metrics and rate limiting statistics



- Schema in `internal/clickhouse/schema/`





**Redis/Upstash**: Distributed caching and rate limiting state



- Multi-tier caching strategy



- Real-time rate limit counters





### Shared Packages (`internal/`)





Key packages for cross-app functionality:



- `@unkey/db`: Database schemas and connections



- `@unkey/cache`: Multi-tier caching implementation



- `@unkey/encryption`: AES-GCM encryption utilities



- `@unkey/keys`: Key generation and validation



- `@unkey/ui`: Shared React components



- `@unkey/validation`: Zod schema definitions





## Development Guidelines





### Code Standards





**TypeScript/JavaScript**:



- Use Biome for formatting and linting



- Prefer named exports over default exports (except Next.js pages)



- Follow strict TypeScript configuration



- Use Zod for runtime validation





**Go**:



- Follow comprehensive documentation guidelines (see `go/GO_DOCUMENTATION_GUIDELINES.md`)



- Every public function/type must be documented



- Use `doc.go` files for package documentation



- Prefer interfaces for testability





### Testing Patterns





**TypeScript**:



- Vitest for unit and integration tests



- Separate configs: `vitest.unit.ts`, `vitest.integration.ts`



- Integration harness for API testing





**Go**:



- Table-driven tests



- Integration tests with real dependencies



- Test organization by HTTP status codes





### Environment Variables





All environment variables must follow the format: `UNKEY_<SERVICE_NAME>_VARNAME`





### Key Patterns





**Authentication/Authorization**:



- Root keys for API access with granular permissions



- Workspace-based multi-tenancy



- RBAC with role inheritance





**Rate Limiting**:



- Distributed consensus for accuracy



- Sliding window algorithm



- Override capabilities for specific identifiers





**Error Handling**:



- Consistent error types with proper HTTP status codes



- Structured error responses following OpenAPI spec



- Circuit breaker patterns for external dependencies





**Caching**:



- Multi-tier strategy (Memory → Redis → Database)



- Stale-while-revalidate pattern



- Namespace-based cache invalidation





## Important Files





- `turbo.json`: Monorepo build configuration



- `biome.json`: Code formatting and linting rules



- `package.json`: Root package with workspace scripts



- `vitest.workspace.json`: Test workspace configuration



- `go/GO_DOCUMENTATION_GUIDELINES.md`: Go code documentation standards



- `go/deploy/CLAUDE.md`: Additional rules for deploy services





## Development Tips





1. **Database Changes**: Use Drizzle migrations, not manual SQL



2. **Testing**: Run integration tests locally with `make integration`



3. **Go Services**: Use `AIDEV-*` comments for complex/important code



4. **Performance**: Prioritize reliability over performance



5. **Security**: Never commit secrets or expose sensitive data in logs

6. **Build**: run the linter and pnpm build after all TODOs No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Fix markdown formatting issues throughout the file.

The documentation content is excellent and comprehensive. However, there are several formatting issues that should be addressed:

  1. Multiple consecutive blank lines: Throughout the file, there are sections with 2-5 consecutive blank lines where only 1 is needed (markdownlint MD012 violations)
  2. Missing final newline: The file should end with a single newline character

To fix these issues systematically:

# Remove excess blank lines and ensure final newline
sed '/^$/N;/^\n$/d' CLAUDE.md > temp && mv temp CLAUDE.md && echo >> CLAUDE.md

Additionally, consider hyphenating compound adjectives for better readability:

  • "rate limiting" → "rate-limiting" when used as an adjective
  • "sliding window" → "sliding-window" when used as an adjective

The documentation structure and content are valuable for AI assistants working with this codebase.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~267-~267: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...ps/agent/`) - Go-based distributed rate limiting service - Uses Serf for clustering...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~275-~275: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...otocol - Implements sliding window rate limiting algorithm Go Services (`go...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~327-~327: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...es data - Verification metrics and rate limiting statistics - Schema in `internal/c...

(EN_COMPOUND_ADJECTIVE_INTERNAL)


[uncategorized] ~337-~337: If this is a compound adjective that modifies the following noun, use a hyphen.
Context: ...edis/Upstash**: Distributed caching and rate limiting state - Multi-tier caching strateg...

(EN_COMPOUND_ADJECTIVE_INTERNAL)

🪛 markdownlint-cli2 (0.17.2)

3-3: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


4-4: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


5-5: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


6-6: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


9-9: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


10-10: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


11-11: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


12-12: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


15-15: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


16-16: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


17-17: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


18-18: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


21-21: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


22-22: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


57-57: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


58-58: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


59-59: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


60-60: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


63-63: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


64-64: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


119-119: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


120-120: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


121-121: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


122-122: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


125-125: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


126-126: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


165-165: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


166-166: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


167-167: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


168-168: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


171-171: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


172-172: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


205-205: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


206-206: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


207-207: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


208-208: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


211-211: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


212-212: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


213-213: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


214-214: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


217-217: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


218-218: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


219-219: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


220-220: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


223-223: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


224-224: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


225-225: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


226-226: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


229-229: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


230-230: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


233-233: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


234-234: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


237-237: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


238-238: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


241-241: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


242-242: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


243-243: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


244-244: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


247-247: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


248-248: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


251-251: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


252-252: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


255-255: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


256-256: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


259-259: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


260-260: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


261-261: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


262-262: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


265-265: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


266-266: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


269-269: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


270-270: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


273-273: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


274-274: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


277-277: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


278-278: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


279-279: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


280-280: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


283-283: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


284-284: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


287-287: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


288-288: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


291-291: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


292-292: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


295-295: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


296-296: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


297-297: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


298-298: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


301-301: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


302-302: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


303-303: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


304-304: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


307-307: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


308-308: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


311-311: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


312-312: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


315-315: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


316-316: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


319-319: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


320-320: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


321-321: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


322-322: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


325-325: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


326-326: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


329-329: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


330-330: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


333-333: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


334-334: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


335-335: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


336-336: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


339-339: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


340-340: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


343-343: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


344-344: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


347-347: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


348-348: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


349-349: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


350-350: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


353-353: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


354-354: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


355-355: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


356-356: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


359-359: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


360-360: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


363-363: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


364-364: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


367-367: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


368-368: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


371-371: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


372-372: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


375-375: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


376-376: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


379-379: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


380-380: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


383-383: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


384-384: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


385-385: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


386-386: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


389-389: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


390-390: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


391-391: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


392-392: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


395-395: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


396-396: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


397-397: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


398-398: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


401-401: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


402-402: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


405-405: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


406-406: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


409-409: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


410-410: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


413-413: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


414-414: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


417-417: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


418-418: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


419-419: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


420-420: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


423-423: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


424-424: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


427-427: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


428-428: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


431-431: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


432-432: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


435-435: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


436-436: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


439-439: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


440-440: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


441-441: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


442-442: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


445-445: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


446-446: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


447-447: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


448-448: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


451-451: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


452-452: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


455-455: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


456-456: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


459-459: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


460-460: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


463-463: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


464-464: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


465-465: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


466-466: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


469-469: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


470-470: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


473-473: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


474-474: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


477-477: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


478-478: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


481-481: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


482-482: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


483-483: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


484-484: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


487-487: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


488-488: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


489-489: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


490-490: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


493-493: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


494-494: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


495-495: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


496-496: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


499-499: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


500-500: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


501-501: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


502-502: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


505-505: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


506-506: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


509-509: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


510-510: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


513-513: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


514-514: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


517-517: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


518-518: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


519-519: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


520-520: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


523-523: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


524-524: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


527-527: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


528-528: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


531-531: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


532-532: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


535-535: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


536-536: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


537-537: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


538-538: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


541-541: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


542-542: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


545-545: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


546-546: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


549-549: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


550-550: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


553-553: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


554-554: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


555-555: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


556-556: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


559-559: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


560-560: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


563-563: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


564-564: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


567-567: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


568-568: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


571-571: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


572-572: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


573-573: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


574-574: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


577-577: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


578-578: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


579-579: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


580-580: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


583-583: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


584-584: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


587-587: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


588-588: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


591-591: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


592-592: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


595-595: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


596-596: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


599-599: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


600-600: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


603-603: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


604-604: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


605-605: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


606-606: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


609-609: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


610-610: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


611-611: Multiple consecutive blank lines
Expected: 1; Actual: 4

(MD012, no-multiple-blanks)


612-612: Multiple consecutive blank lines
Expected: 1; Actual: 5

(MD012, no-multiple-blanks)


615-615: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


616-616: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


619-619: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


620-620: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


623-623: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


624-624: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


627-627: Multiple consecutive blank lines
Expected: 1; Actual: 2

(MD012, no-multiple-blanks)


628-628: Multiple consecutive blank lines
Expected: 1; Actual: 3

(MD012, no-multiple-blanks)


631-631: Files should end with a single newline character

(MD047, single-trailing-newline)

🤖 Prompt for AI Agents
In CLAUDE.md from lines 1 to 631, fix markdown formatting by reducing multiple
consecutive blank lines to a single blank line throughout the file to comply
with markdownlint MD012. Also, ensure the file ends with exactly one newline
character. Additionally, hyphenate compound adjectives such as "rate limiting"
to "rate-limiting" and "sliding window" to "sliding-window" when used
adjectivally for improved readability.

Comment on lines +56 to +58
// Determine environment (default to preview)
// TODO: Add environment field to CreateVersionRequest proto
environment := db.DeploymentsEnvironmentPreview
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Add the environment field to the CreateVersionRequest proto.

The TODO comment indicates that the environment field needs to be added to the protobuf definition to allow clients to specify environments other than the hardcoded "preview" default.

Do you want me to help track this as a follow-up issue or generate the proto changes needed?

🤖 Prompt for AI Agents
In go/apps/ctrl/services/deployment/create_version.go around lines 56 to 58, the
code currently defaults the environment to "preview" because the
CreateVersionRequest protobuf lacks an environment field. To fix this, add an
environment field to the CreateVersionRequest protobuf definition so clients can
specify the environment. Then update the code to read the environment from the
request instead of hardcoding it. This requires modifying the proto file,
regenerating the Go code, and updating this service to use the new field.

@@ -8,16 +8,16 @@ import (
)

func (s *Service) loadVersionSpec(ctx context.Context, versionID string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider renaming parameter for clarity.

The parameter versionID should be renamed to deploymentID to reflect that it now identifies a deployment rather than a version.

-func (s *Service) loadVersionSpec(ctx context.Context, versionID string) (string, error) {
+func (s *Service) loadVersionSpec(ctx context.Context, deploymentID string) (string, error) {
🤖 Prompt for AI Agents
In go/apps/ctrl/services/openapi/utils.go at line 10, rename the parameter
`versionID` to `deploymentID` in the function signature of loadVersionSpec to
accurately reflect that it identifies a deployment instead of a version. Update
all references to this parameter within the function accordingly to maintain
consistency.

Comment on lines +6 to 7
environment,
build_id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Query still selects every column – add projection helper once use-sites stabilise.

Right now UI calls often need only a subset (status, git info, timestamps).
Providing a lighter FindDeploymentSummaryById query later would reduce row size and bandwidth without altering this PR’s behaviour.

🤖 Prompt for AI Agents
In go/pkg/db/queries/deployment_find_by_id.sql around lines 6 to 7, the query
currently selects all columns, which can be inefficient. To fix this, plan to
add a projection helper that selects only the necessary columns (like status,
git info, timestamps) once the usage patterns stabilize. For now, keep the full
selection unchanged to avoid breaking existing functionality, but prepare to
implement a lighter query variant named FindDeploymentSummaryById in the future
to optimize performance.

Comment on lines +6 to 7
environment,
build_id,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

environment column – enforce allowed values.

Because environment is now a free-text varchar, bad or inconsistent values can slip in.
Consider constraining it with:

  1. A CHECK constraint in the migration (environment IN ('dev','staging','prod',...)), or
  2. Switching to an ENUM / dedicated lookup table.

This will prevent subtle prod / staging mix-ups later.

Also applies to: 21-22

🤖 Prompt for AI Agents
In go/pkg/db/queries/deployment_insert.sql around lines 6-7 and 21-22, the
environment column currently allows free-text values, which can lead to
inconsistent or invalid entries. To fix this, add a CHECK constraint in the
migration to restrict environment values to a predefined set like ('dev',
'staging', 'prod'), or alternatively, change the column to use an ENUM type or
reference a dedicated lookup table that enforces allowed values. This will
ensure only valid environment names are stored.

Comment on lines +8 to +10
FROM deployment_steps
WHERE deployment_id = ?
ORDER BY created_at ASC; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Add (or confirm) an index on deployment_id for this hot path.

Query execution will scan deployment_steps for every deployment detail page / workflow poll.
Ensure CREATE INDEX idx_deployment_steps_deployment_id ON deployment_steps (deployment_id); exists to keep look-ups O(log n).

🤖 Prompt for AI Agents
In go/pkg/db/queries/deployment_step_find_by_deployment_id.sql around lines 8 to
10, the query filters on deployment_id but there is no confirmation that an
index exists on this column. To optimize query performance, ensure that a
database index named idx_deployment_steps_deployment_id is created on the
deployment_id column by running CREATE INDEX idx_deployment_steps_deployment_id
ON deployment_steps (deployment_id); This will speed up lookups by avoiding full
table scans.

Comment on lines +2 to +4
UPDATE deployments
SET openapi_spec = ?, updated_at = ?
WHERE id = ?; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Consider DB-side timestamp to avoid clock skew.

updated_at is supplied as a parameter. If callers rely on different machine clocks this can drift; using NOW() (or equivalent) in SQL keeps the DB authoritative:

-SET openapi_spec = ?, updated_at = ?
+SET openapi_spec = ?, updated_at = NOW()

Just a nicety—ignore if you already standardise timestamps in Go.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
UPDATE deployments
SET openapi_spec = ?, updated_at = ?
WHERE id = ?;
UPDATE deployments
SET openapi_spec = ?, updated_at = NOW()
WHERE id = ?;
🤖 Prompt for AI Agents
In go/pkg/db/queries/deployment_update_openapi_spec.sql at lines 2 to 4, the
updated_at field is set via a parameter which can cause clock skew if callers
have different system times. Modify the SQL to set updated_at using the
database's current timestamp function (e.g., NOW()) instead of passing it as a
parameter, ensuring the timestamp is consistent and authoritative from the DB
side.

Comment on lines +2 to +4
UPDATE deployments
SET status = ?, updated_at = ?
WHERE id = ?; No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Same timestamp consideration as above.

Optional: switch updated_at = NOW() so the DB owns the write time.

🤖 Prompt for AI Agents
In go/pkg/db/queries/deployment_update_status.sql around lines 2 to 4, the
update statement sets updated_at via a parameter, which relies on the
application to provide the timestamp. To ensure consistent and accurate write
time, modify the query to set updated_at = NOW() directly in the SQL so the
database manages the timestamp. Remove the updated_at parameter from the query
and adjust the code calling this query accordingly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1cf38ec and 0412787.

📒 Files selected for processing (1)
  • apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/list.ts:11-11
Timestamp: 2025-07-25T19:09:43.284Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/list.ts, the listDeployments procedure intentionally queries the versions table rather than a deployments table. The user mcstepp indicated that renaming the table would require a database migration, which was deferred for the current PR focused on UI features.
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts:110-147
Timestamp: 2025-07-25T19:11:00.208Z
Learning: In apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts, the user mcstepp prefers to keep mock data fallbacks in POC/demonstration code for simplicity, even if it wouldn't be production-ready. This aligns with the PR being work-in-progress for demonstration purposes.
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/page.tsx:74-81
Timestamp: 2025-07-28T19:42:37.047Z
Learning: In apps/dashboard/app/(app)/projects/page.tsx, the user mcstepp prefers to keep placeholder functions like generateSlug inline during POC/demonstration phases rather than extracting them to utility modules, with plans to refactor later when the feature matures beyond the proof-of-concept stage.
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/lib/trpc/routers/branch/getByName.ts:0-0
Timestamp: 2025-07-28T20:36:36.865Z
Learning: In apps/dashboard/lib/trpc/routers/branch/getByName.ts, mcstepp prefers to keep mock data (gitCommitMessage, buildDuration, lastCommitAuthor, etc.) in the branch procedure during POC phases to demonstrate what the UI would look like with proper schema changes, rather than returning null/undefined values.
Learnt from: mcstepp
PR: unkeyed/unkey#3662
File: apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx:322-341
Timestamp: 2025-07-28T20:38:53.244Z
Learning: In apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/components/client.tsx, mcstepp prefers to keep hardcoded endpoint logic in the getDiffType function during POC phases for demonstrating diff functionality, rather than implementing a generic diff algorithm. This follows the pattern of keeping simplified implementations for demonstration purposes.
📚 Learning: in the rate limit test files (e.g., `apps/api/src/routes/v1_ratelimit_getoverride.happy.test.ts`), u...
Learnt from: chronark
PR: unkeyed/unkey#2126
File: apps/api/src/routes/v1_ratelimit_getOverride.happy.test.ts:36-36
Timestamp: 2024-11-13T19:06:36.786Z
Learning: In the rate limit test files (e.g., `apps/api/src/routes/v1_ratelimit_getOverride.happy.test.ts`), URL parameters like `namespaceId` and `identifier` do not need to be URL-encoded in the test code because the values used are always considered safe within the test environment.

Applied to files:

  • apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts
📚 Learning: the codebase for unkey allows the use of modern javascript features like array.prototype.tosorted() ...
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/table/hooks/use-logs-query.ts:228-231
Timestamp: 2025-05-15T16:21:50.911Z
Learning: The codebase for Unkey allows the use of modern JavaScript features like Array.prototype.toSorted() even though they may not be supported in all browsers.

Applied to files:

  • apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Build / Build
  • GitHub Check: Test Packages / Test
  • GitHub Check: detect_changes / Detect Changes
🔇 Additional comments (1)
apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts (1)

1067-1067: Good practice: Using fixed timestamp for test determinism.

Replacing Date.now() with a fixed timestamp makes the test reproducible and eliminates potential flakiness from time-dependent behavior.


expect(res.status, `expected 200, received: ${JSON.stringify(res, null, 2)}`).toBe(200);

console.log(res);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Remove console.log before merging.

Debug logging should not be committed to the main branch.

-  console.log(res);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
console.log(res);
@@ -1107,1 +1107,0 @@
- console.log(res);
🤖 Prompt for AI Agents
In apps/api/src/routes/v1_analytics_getVerifications.happy.test.ts at line 1107,
remove the console.log statement used for debugging before merging to keep the
main branch clean from debug logs.

@chronark chronark merged commit 2a4eb5a into main Aug 1, 2025
19 checks passed
@chronark chronark deleted the ENG-1902-projects-ui branch August 1, 2025 17:22
chronark added a commit that referenced this pull request Aug 7, 2025
* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* changes before merge

* chore: deprecate v1 endpoints (#3680)

* fix: openapi

* chore: deprecate v1 endpoints

* fix: vault credentials and chproxy config (#3681)

* fix: openapi

* fix: vault credentials and chproxy config

* fix: rename flag accessor too

* fix: linter issues

* fix: some more v2 api changes (#3677)

* remove namespaceID

* actually use limit and cursor

* filter out delted overrides

* fix error messages list endpoints

* fix more error messages

* ensure identity create handles like permission/role create

* fix regex for roles

* fix regex for roles

* fix list keys cursor

* fix: uppercase common files (#3683)

* name files uppercase

* name files uppercase

* [autofix.ci] apply automated fixes

* name files uppercase

* name files uppercase

---------

Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: conflicting casing (#3689)

* fix the openapi spec again (#3692)

* dont trace chproxy endpoints (#3691)

* fix: log verifications to the owning workspace (#3693)

* functioning again

* style change

* more tweaks

* fix: validate s3 config (#3694)

* Fix all the fucking things

* remove close button

* fmt

* fix: speakeasy ignore directive is ignored if it's a string (#3699)

* fix: upsert permissions with slug or name colission (#3696)

* fix: upsert permissions with slug or name colission

* chore: also remove index

* fix permission test and remove unnnecessary test

---------

Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>

* docs: migration (#3678)

* fix: openapi

* docs: migration from v1 to v2

* fix: remove binaries

* [autofix.ci] apply automated fixes

* docs: add james' feedback

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: api list keys zod errors (#3702)

* fix api zod errors

* [autofix.ci] apply automated fixes

* make array handling uniform

* make array handling uniform

* fix rabbi comment

* fix: permission array for roles

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* ci: don't build for windows and freebsd (#3700)

* docs: errors (#3703)

* chore: move sdks to unkeyed/sdks (#3701)

* fix: omitting array vs null (#3704)

* fix omitting array vs null

* [autofix.ci] apply automated fixes

* fix flakey test

* fix flakey test

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: panics not being catched (#3706)

* fix panics not being catched

* feat: add counter to track panics

Signed-off-by: Ian Meyer <k@imeyer.io>

---------

Signed-off-by: Ian Meyer <k@imeyer.io>
Co-authored-by: Ian Meyer <k@imeyer.io>

* docs: use `CodeGroup` in hono/nextjs TS libraries (#3708)

* Update hono.mdx

* Update nextjs.mdx

* ci: remove outdated steps and flows (#3709)

* docs: update sdks (#3712)

* docs: update sdks

* Update nextjs.mdx

* [autofix.ci] apply automated fixes

* fix: rabbit feedback

* Update nextjs.mdx

* fix: root key is required

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* dialog and confirm added

* feat(deployment beta): projects UI for Unkey Deploy (#3662)

* projects and branches

* wip

* wip

* spec differ wip

* fix some docker, add some trpc, integrate diff viewer

* change version to deployments, add feature flag, update go schema

* update versions page

* fix null condition

* delete old router, fix null assertion

* fmt

* fmt

* fmt again

* apply auth and feature flagging access to projects, remove versions

* yolo

* stable yolo

* stable yolo

* style: fmt

* fix: hardcode time, so it doesn't fail on the first of a month

* [autofix.ci] apply automated fixes

---------

Co-authored-by: chronark <dev@chronark.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* chore: add missingd delete endpoint for v2 (#3711)

* only log key at the end (#3716)

* fix: allow wildcard and colon in permissions query (#3717)

* remove regex for permissions

* allow for asterix and colon in permissions

* fix: update identity by identity key instead of externalId and fix wrong body for permission and role (#3713)

* docs and remove externalId from keyResponse

* fix updateIdentity to take in an identity parameter instead of an externalId

* fix get role/permission

* Update go/apps/api/openapi/spec/common/Permission.yaml

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update openapi-generated.yaml

* fix comment

---------

Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* chore: openapi references (#3723)

* feat: add paginated tRPC endpoint for projects (#3697)

* feat: add new endpoint for deploy projects

* chore: replace file path

* [autofix.ci] apply automated fixes

* feat: add missing endpoint

* fix: trpc path

* fix: add feature flag

* chore: remove optin

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: region not showing and wrong rl id (#3722)

* fix: region not showing and wrong rl id

* pass region down

* perf: bad get key performance (#3724)

* perf: make getKey 2 seperate queries so mysql chooses correct idx

* fix query

* fix query name

* docs: verify identities endpoints (#3727)

* chore: docs (#3728)

* chore: fixup migration guide

* adjust more

* adjust more

* adjust more

* rabbit comments

* Update index.mdx

* working updates

* re factor for clarity

* only update if diff than existing

* [autofix.ci] apply automated fixes

* re name create-root-key to root-key folder

---------

Signed-off-by: Ian Meyer <k@imeyer.io>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>
Co-authored-by: Oğuzhan Olguncu <21091016+ogzhanolguncu@users.noreply.github.com>
Co-authored-by: James Perkins <jamesperkins@hey.com>
Co-authored-by: Ian Meyer <k@imeyer.io>
Co-authored-by: JA Castro <51177379+ubinatus@users.noreply.github.com>
Co-authored-by: Meg Stepp <mcstepp@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
This was referenced Aug 11, 2025
perkinsjr added a commit that referenced this pull request Aug 20, 2025
* chore: new root key dialog (#3637)

* Dialog created

* small changes

* feat: ui show permission side bar when select permission button is (#3654)

* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* feat: UI when a permission is selected it should show in the modal (#3663)

* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* button size and margin

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* feat: UI when a user searches for a permission it should show (#3695)

* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* changes before merge

* functioning again

* style change

* more tweaks

* Fix all the fucking things

* remove close button

* fmt

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: James Perkins <jamesperkins@hey.com>

* feat: rootkey create with success dialog and confirm close (#3714)

* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* changes before merge

* functioning again

* style change

* more tweaks

* Fix all the fucking things

* remove close button

* fmt

* dialog and confirm added

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: James Perkins <jamesperkins@hey.com>

* feat: a user selects edit root key and an edit root key modal (#3731)

* Dialog created

* small changes

* half functional

* small spacing changes and chevron

* Rabbit Changes

* remove useffect

* almost

* re ordered badge list collapse

* undo rabbit

* [autofix.ci] apply automated fixes

* fix scroll

* changes before merge

* chore: deprecate v1 endpoints (#3680)

* fix: openapi

* chore: deprecate v1 endpoints

* fix: vault credentials and chproxy config (#3681)

* fix: openapi

* fix: vault credentials and chproxy config

* fix: rename flag accessor too

* fix: linter issues

* fix: some more v2 api changes (#3677)

* remove namespaceID

* actually use limit and cursor

* filter out delted overrides

* fix error messages list endpoints

* fix more error messages

* ensure identity create handles like permission/role create

* fix regex for roles

* fix regex for roles

* fix list keys cursor

* fix: uppercase common files (#3683)

* name files uppercase

* name files uppercase

* [autofix.ci] apply automated fixes

* name files uppercase

* name files uppercase

---------

Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: conflicting casing (#3689)

* fix the openapi spec again (#3692)

* dont trace chproxy endpoints (#3691)

* fix: log verifications to the owning workspace (#3693)

* functioning again

* style change

* more tweaks

* fix: validate s3 config (#3694)

* Fix all the fucking things

* remove close button

* fmt

* fix: speakeasy ignore directive is ignored if it's a string (#3699)

* fix: upsert permissions with slug or name colission (#3696)

* fix: upsert permissions with slug or name colission

* chore: also remove index

* fix permission test and remove unnnecessary test

---------

Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>

* docs: migration (#3678)

* fix: openapi

* docs: migration from v1 to v2

* fix: remove binaries

* [autofix.ci] apply automated fixes

* docs: add james' feedback

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: api list keys zod errors (#3702)

* fix api zod errors

* [autofix.ci] apply automated fixes

* make array handling uniform

* make array handling uniform

* fix rabbi comment

* fix: permission array for roles

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* ci: don't build for windows and freebsd (#3700)

* docs: errors (#3703)

* chore: move sdks to unkeyed/sdks (#3701)

* fix: omitting array vs null (#3704)

* fix omitting array vs null

* [autofix.ci] apply automated fixes

* fix flakey test

* fix flakey test

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: panics not being catched (#3706)

* fix panics not being catched

* feat: add counter to track panics

Signed-off-by: Ian Meyer <k@imeyer.io>

---------

Signed-off-by: Ian Meyer <k@imeyer.io>
Co-authored-by: Ian Meyer <k@imeyer.io>

* docs: use `CodeGroup` in hono/nextjs TS libraries (#3708)

* Update hono.mdx

* Update nextjs.mdx

* ci: remove outdated steps and flows (#3709)

* docs: update sdks (#3712)

* docs: update sdks

* Update nextjs.mdx

* [autofix.ci] apply automated fixes

* fix: rabbit feedback

* Update nextjs.mdx

* fix: root key is required

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* dialog and confirm added

* feat(deployment beta): projects UI for Unkey Deploy (#3662)

* projects and branches

* wip

* wip

* spec differ wip

* fix some docker, add some trpc, integrate diff viewer

* change version to deployments, add feature flag, update go schema

* update versions page

* fix null condition

* delete old router, fix null assertion

* fmt

* fmt

* fmt again

* apply auth and feature flagging access to projects, remove versions

* yolo

* stable yolo

* stable yolo

* style: fmt

* fix: hardcode time, so it doesn't fail on the first of a month

* [autofix.ci] apply automated fixes

---------

Co-authored-by: chronark <dev@chronark.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* chore: add missingd delete endpoint for v2 (#3711)

* only log key at the end (#3716)

* fix: allow wildcard and colon in permissions query (#3717)

* remove regex for permissions

* allow for asterix and colon in permissions

* fix: update identity by identity key instead of externalId and fix wrong body for permission and role (#3713)

* docs and remove externalId from keyResponse

* fix updateIdentity to take in an identity parameter instead of an externalId

* fix get role/permission

* Update go/apps/api/openapi/spec/common/Permission.yaml

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update openapi-generated.yaml

* fix comment

---------

Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* chore: openapi references (#3723)

* feat: add paginated tRPC endpoint for projects (#3697)

* feat: add new endpoint for deploy projects

* chore: replace file path

* [autofix.ci] apply automated fixes

* feat: add missing endpoint

* fix: trpc path

* fix: add feature flag

* chore: remove optin

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>

* fix: region not showing and wrong rl id (#3722)

* fix: region not showing and wrong rl id

* pass region down

* perf: bad get key performance (#3724)

* perf: make getKey 2 seperate queries so mysql chooses correct idx

* fix query

* fix query name

* docs: verify identities endpoints (#3727)

* chore: docs (#3728)

* chore: fixup migration guide

* adjust more

* adjust more

* adjust more

* rabbit comments

* Update index.mdx

* working updates

* re factor for clarity

* only update if diff than existing

* [autofix.ci] apply automated fixes

* re name create-root-key to root-key folder

---------

Signed-off-by: Ian Meyer <k@imeyer.io>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>
Co-authored-by: Oğuzhan Olguncu <21091016+ogzhanolguncu@users.noreply.github.com>
Co-authored-by: James Perkins <jamesperkins@hey.com>
Co-authored-by: Ian Meyer <k@imeyer.io>
Co-authored-by: JA Castro <51177379+ubinatus@users.noreply.github.com>
Co-authored-by: Meg Stepp <mcstepp@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fresh

* start rabbit changes

* rabbits

* missing processing

* Update apps/dashboard/lib/trpc/routers/index.ts

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update apps/dashboard/app/(app)/settings/root-keys/components/root-key/components/permission-badge-list.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* more rabbit

* function name

* [autofix.ci] apply automated fixes

* minor changes

* rabbit checked locally

* comment changes

* light mode fix

* removed old page files

* updated success

* slack comment changes

* cleanup

* rabbit

* more rabbits

* most consts

* change clear and details

* cleanup

* pr suggested changes

* few nits

---------

Signed-off-by: Ian Meyer <k@imeyer.io>
Co-authored-by: CodeReaper <148160799+MichaelUnkey@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Andreas Thomas <dev@chronark.com>
Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>
Co-authored-by: Oğuzhan Olguncu <21091016+ogzhanolguncu@users.noreply.github.com>
Co-authored-by: Ian Meyer <k@imeyer.io>
Co-authored-by: JA Castro <51177379+ubinatus@users.noreply.github.com>
Co-authored-by: Meg Stepp <mcstepp@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: MichaelUnkey <michael@unkey.com>
@coderabbitai coderabbitai bot mentioned this pull request Oct 3, 2025
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Projects UI

2 participants