Skip to content

feat: load deployment details UI from db #3942

Merged
chronark merged 21 commits intomainfrom
refactor_migrate_projects_to_TanStack_React_DB
Sep 15, 2025
Merged

feat: load deployment details UI from db #3942
chronark merged 21 commits intomainfrom
refactor_migrate_projects_to_TanStack_React_DB

Conversation

@chronark
Copy link
Collaborator

@chronark chronark commented Sep 10, 2025

What does this PR do?

skipping this in favour of a quick huddle with oz

Summary by CodeRabbit

  • New Features

    • Projects, deployments, environments, and domains now load live in the dashboard; deployments and diff pages show joined environment context.
    • Active Deployment card now fetches and displays real deployment details and status indicators.
  • Improvements

    • Dynamic Environment filter sourced from your project’s environments.
    • Simplified deployment statuses and updated badges (pending, building, deploying, network, ready, failed).
    • Deployments table shows richer git/runtime info; compact views improved.
    • Time range control defaults to “Select Time Range”.
    • Projects list adds loading skeletons; Create Project accepts empty repo URL; IDs shortened for readability.

@vercel
Copy link

vercel bot commented Sep 10, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
dashboard Ready Ready Preview Comment Sep 15, 2025 11:11am
1 Skipped Deployment
Project Deployment Preview Comments Updated (UTC)
engineering Ignored Ignored Preview Sep 15, 2025 11:11am

@changeset-bot
Copy link

changeset-bot bot commented Sep 10, 2025

⚠️ No Changeset found

Latest commit: c81659b

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

📝 Walkthrough

Walkthrough

Refactors dashboard data access from TRPC hooks to TanStack React‑DB live collections (projects, deployments, environments, domains), flattens TRPC router structure, updates UI components and hooks to new data shapes and prop-driven APIs, adds collections/types, and adds projects.active_deployment_id DB column; numerous legacy hooks/routes removed.

Changes

Cohort / File(s) Summary of changes
Deployments controls
apps/.../deployment-list-datetime/index.tsx, .../deployment-list-filters/components/deployment-status-filter.tsx, .../deployment-list-filters/components/environment-filter.tsx, .../deployment-list-search/index.tsx
Datetime title default and reset via useEffect; status options renamed/grouped; environment options sourced from live collection (slug); TRPC search path updated.
Deployments table & utils
apps/.../table/deployments-list.tsx, .../table/components/deployment-status-badge.tsx, .../table/components/actions/deployment-list-table-action.popover.constants.tsx, .../table/utils/get-row-class.ts, .../table/hooks/use-deployments-list-query.ts
Table switched to useLiveQuery deployments+environments (new data shape), status mappings updated, Deployment type import moved to collections, row selection util signature changed, legacy infinite-query hook deleted.
Deployment filters schema
apps/.../deployments/filters.schema.ts
Reworked statuses into grouped types, added DeploymentEnvironment and typed filter configs/enums/utilities, and created a typed output schema.
Deployment pages & diff
apps/.../deployments/page.tsx, .../diff/[...compare]/page.tsx, .../diff/page.tsx
Pages now use live queries for deployments+environments; label/formatting adjusted for new shape; loading/empty states updated.
Active deployment & details
apps/.../details/active-deployment-card/index.tsx, .../hooks/use-deployment-logs.tsx, .../skeleton.tsx, .../project-details-expandables/*
ActiveDeploymentCard becomes prop-driven (deploymentId) and reads via collections; STATUS_CONFIG replaced by statusIndicator; logs TRPC path adjusted; details sections updated to new Deployment shape with placeholders.
Project page & navigation
apps/.../projects/[projectId]/page.tsx, .../layout.tsx, .../layout-provider.tsx, .../navigations/project-navigation.tsx, components/navigation/sidebar/...
Project page and nav use live queries; activeDeploymentId removed from layout context; navigation lists via collections and load-more removed.
Environment variables UI
apps/.../details/env-variables-section/*
TRPC invalidation removed/stubbed; list query points to environmentVariables.list; AddEnvVarRow props simplified; save/delete invalidations left TODO.
Projects list & actions
apps/.../_components/list/index.tsx, .../project-actions.tsx, .../hooks/use-projects-list-query.ts
Projects list switched to live query with skeletons; ProjectActions now takes projectId string; legacy hook deleted and pagination removed.
Create project flow
apps/.../_components/create-project/*
gitRepositoryUrl preserved as "" on submit; schema allows null/""; TRPC mutation path changed and cache invalidation removed.
Collections (React‑DB)
apps/dashboard/lib/collections/*.ts, apps/dashboard/lib/collections/index.ts
Added projects, deployments, domains, environments collections with Zod schemas and exported types; reset() aggregator added; ratelimit types exported.
TRPC router restructuring
apps/dashboard/lib/trpc/routers/index.ts, .../project/list.ts, .../project/create.ts, .../domains/list.ts, .../environment/list.ts, .../deployment/*, deleted deploy/project/*
Flattened router to top-level project/domain/deployment/environment/environmentVariables; added list endpoints; renamed LLM search export; removed legacy deploy/project nested routes.
Server DB schema & mapping
go/pkg/db/schema.sql, internal/db/src/schema/projects.ts
Added nullable projects.active_deployment_id column and corresponding internal schema mapping (activeDeploymentId).
Utility
apps/dashboard/lib/shorten-id.ts
Prefix-aware shortening added, default startChars changed to 4, validations added; public signature unchanged.
Misc updates
various UI files across deployments, details, projects
Path and import updates to use new TRPC endpoints/collections; several components changed props (e.g., DeploymentsList requires projectId).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant UI as DeploymentsList (client)
  participant DB as React‑DB collections
  participant API as TRPC

  User->>UI: Open deployments page
  UI->>DB: useLiveQuery(collection.environments) & useLiveQuery(collection.deployments, { projectId })
  DB->>API: fetch via trpc (environment.list, deployment.list)
  API-->>DB: arrays of rows
  DB-->>UI: { isLoading, data }
  UI->>UI: apply client-side filters & render joined rows
Loading
sequenceDiagram
  autonumber
  participant User
  participant Page as Project Page
  participant DB as React‑DB collections
  participant API as TRPC

  User->>Page: Navigate to /projects/:projectId
  Page->>DB: useLiveQuery(collection.projects) -> find project
  Page->>DB: if project.activeDeploymentId -> useLiveQuery(collection.deployments.byId)
  DB->>API: trpc project.list / deployment.list as needed
  DB-->>Page: project/deployment data
  Page-->>User: Render project overview + ActiveDeploymentCard(deploymentId) or empty state
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested labels

Core Team, Dashboard, UI

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description only says "skipping this in favour of a quick huddle with oz" and does not follow the repository's required template: it omits the detailed "What does this PR do?" summary, a linked issue, the "Type of change" checklist, the "How should this be tested?" steps, and the required checklist items, leaving reviewers without context or testing instructions. Because the template is not completed and critical information about migration, testing, and breaking changes is missing, the description is insufficient for review. Please complete the repository PR template: add a clear summary and linked issue, mark the appropriate "Type of change", and fill the Checklist. Provide explicit "How should this be tested?" steps (commands to run like pnpm build/fmt, manual UI verification steps to confirm deployments now load from the DB, and any migration steps for the new active_deployment_id column). Also document compatibility/migration impacts (removed TRPC routes, new collections, and public signature changes) and attach screenshots or a short recording for UI changes to help reviewers validate behavior.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: load deployment details UI from db" is concise, uses the conventional feat: prefix, and accurately summarizes the primary change—switching the deployment details UI data source to the database—matching the changes in the diff (collections, useLiveQuery, and removed TRPC endpoints). It is specific enough for a reviewer scanning history and avoids noise.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor_migrate_projects_to_TanStack_React_DB

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 324c314 and c81659b.

📒 Files selected for processing (1)
  • apps/dashboard/lib/trpc/routers/environment/list.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/lib/trpc/routers/environment/list.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). (5)
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Analyze (javascript-typescript)

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Collaborator Author

chronark commented Sep 10, 2025

This stack of pull requests is managed by Graphite. Learn more about stacking.

@chronark chronark mentioned this pull request Sep 10, 2025
18 tasks
@chronark chronark changed the title refactor: migrate projects to TanStack React DB feat: load deployment details UI from db Sep 10, 2025
chronark and others added 11 commits September 10, 2025 09:20
* feat: sync drizzle to go/pkg/db/schema.sql

* fix: upgrade drizzle-kit to use required flag

* fix: autogeneration notice

---------

Co-authored-by: Flo <53355483+Flo4604@users.noreply.github.com>
* feat: script to notify users to migrate to v2

* Apply suggestions from code review

* [autofix.ci] apply automated fixes

* fix: address james' feedback

* merge

* fix: exclude analytics

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
@chronark chronark force-pushed the refactor_migrate_projects_to_TanStack_React_DB branch from f5dc233 to 64aa43c Compare September 10, 2025 07:27
@chronark chronark force-pushed the 09-07-feat_local_first_ratelimits branch from 9b3e5bc to dd64beb Compare September 10, 2025 07:27
@github-actions
Copy link
Contributor

github-actions bot commented Sep 15, 2025

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

@chronark
Copy link
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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: 51

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (37)
apps/dashboard/lib/shorten-id.ts (3)

8-11: Docs out of sync with actual defaults.

  • startChars comment still says “default: 8” but code default is 4.
  • minLength comment hardcodes “+ 3” but should reflect the separator length.

Apply this doc-only diff:

-    /** Number of characters to keep from the start (default: 8) */
+    /** Number of characters to keep from the start (default: 4) */
@@
-    /** Minimum length required to apply shortening (default: startChars + endChars + 3) */
+    /** Minimum length required to apply shortening (default: startChars + endChars + separator.length) */

Also applies to: 14-15


19-23: Make minLength depend on separator length.

Hardcoding 3 ties behavior to "...". Use the actual separator length instead.

   const {
     startChars = 4,
     endChars = 4,
     separator = "...",
-    minLength = startChars + endChars + 3,
+    minLength = startChars + endChars + separator.length,
   } = options;

Please confirm no callers relied on the previous implicit “+3” behavior with custom separators.


34-43: Use the shortened segment (rest/target) for length checks — fixes duplicated slices

File: apps/dashboard/lib/shorten-id.ts Lines: 34-43 (also applies to 44-53)

Length checks currently use the full id; when a prefix exists and rest is shorter than startChars + endChars this produces duplicated slices (negative-clamped substring). Base the "too short" and "exceeds length" checks on the target being shortened (rest when prefixed; otherwise id).

Apply this refactor:

-  // Return original if too short to meaningfully shorten
-  if (id.length <= minLength) {
-    return id;
-  }
-
-  // Handle edge case where requested chars exceed ID length
-  if (startChars + endChars >= id.length) {
-    return id;
-  }
-
-  const [prefix, rest] = id.includes("_") ? id.split("_", 2) : [null, id];
-  let s = "";
-  if (prefix) {
-    s += prefix;
-    s += "_";
-  }
-  s += rest.substring(0, startChars);
-  s += separator;
-  s += rest.substring(rest.length - endChars);
-  return s;
+  // Determine the target string for length checks
+  const [prefix, rest] = id.includes("_") ? id.split("_", 2) : [null, id];
+  const target = prefix ? rest : id;
+
+  // Return original if too short to meaningfully shorten
+  if (target.length <= minLength) {
+    return id;
+  }
+
+  // Handle edge case where requested chars exceed target length
+  if (startChars + endChars >= target.length) {
+    return id;
+  }
+
+  // Build the shortened string
+  let s = "";
+  if (prefix) {
+    s += prefix;
+    s += "_";
+  }
+  s += rest.slice(0, startChars);
+  s += separator;
+  s += endChars > 0 ? rest.slice(-endChars) : "";
+  return s;

Open question: if the ID starts with "_" (empty prefix) this drops the leading underscore — intentional?

apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/deployment-status-filter.tsx (1)

46-47: Avoid "undefined" leaking into className when getColorClass is absent.

Guard with a fallback to keep the class clean.

Apply this diff:

-          <div className={`size-2 ${getColorClass?.(checkbox.status)} rounded-[2px]`} />
+          <div className={`size-2 ${getColorClass?.(checkbox.status) ?? ""} rounded-[2px]`} />
apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (1)

51-63: Color mapping is outdated: ‘completed’ is gone; ‘ready’ isn’t mapped to success.

This miscolors Ready as info (blue). Replace completed with ready and update the comment.

Apply this diff:

     getColorClass: (value) => {
-      if (value === "completed") {
-        return "bg-success-9";
-      }
+      if (value === "ready") {
+        return "bg-success-9";
+      }
       if (value === "failed") {
         return "bg-error-9";
       }
       if (value === "pending") {
         return "bg-gray-9";
       }
-      return "bg-info-9"; // building
+      return "bg-info-9"; // deploying
     },
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (2)

58-68: Remove redundant transform and use logs for auto‑expand

The select above already returns LogEntry[]. Drop the extra useMemo and switch the effect to logs.

-  const logs = useMemo((): LogEntry[] => {
-    if (!logsData?.logs) {
-      return [];
-    }
-
-    return logsData.logs.map((log) => ({
-      timestamp: format(new Date(log.timestamp), "HH:mm:ss.SSS"),
-      level: log.level,
-      message: log.message,
-    }));
-  }, [logsData]);
+  // Logs are already transformed via `select` in useQuery.
+  // `logs` may be undefined before first fetch; treat as empty array where needed.
   useEffect(() => {
-    if (logsData?.logs && logsData.logs.length > 0) {
+    if ((logs?.length ?? 0) > 0) {
       setIsExpanded(true);
     }
-  }, [logsData]);
+  }, [logs]);

Also applies to: 71-75


31-31: Expose error in the hook’s return type

Lets the card render a failure state or retry affordance without guessing.

   // Loading state
   isLoading: boolean;
+  error: unknown | null;
   // Loading state
   isLoading,
+  error: error ?? null,

Also applies to: 151-151

apps/dashboard/lib/trpc/routers/project/create.ts (4)

102-110: Blocker: environment inserts violate NOT NULL created_at and will fail

environments.created_at is NOT NULL in the schema, but you don’t set it (or updated_at). The transaction will error on project creation.

Apply:

-          for (const slug of ["production", "preview"]) {
+          for (const slug of ["production", "preview"]) {
             const environmentId = newId("environment");
             await tx.insert(schema.environments).values({
               id: environmentId,
               workspaceId,
               projectId,
               slug: slug,
+              createdAt: now,
+              updatedAt: now,
             });

102-110: Blocker: environment slug uniqueness collides across projects within a workspace

Schema enforces UNIQUE(workspace_id, slug) for environments. Auto‑creating "production"/"preview" per project will clash on the second project in the same workspace.

Fix options:

  • Preferred: change unique index to (workspace_id, project_id, slug) and regenerate go schema; migrate existing data.
  • Interim: namespace slugs per project (e.g., ${projectSlug}-production) until the index is widened.

39-61: Race on slug uniqueness → map DB duplicate to TRPC CONFLICT

Between the pre‑check and insert, a concurrent request can win. Today this bubbles as INTERNAL_SERVER_ERROR. Catch duplicate-key and return CONFLICT.

Example:

       } catch (txErr) {
+        // Map unique index violation to CONFLICT
+        if (txErr && typeof txErr === "object") {
+          const code = (txErr as any).code; // e.g. 'ER_DUP_ENTRY' (mysql2)
+          if (code === "ER_DUP_ENTRY" /* or vendor-specific check */) {
+            throw new TRPCError({
+              code: "CONFLICT",
+              message: `A project with slug "${input.slug}" already exists in this workspace`,
+            });
+          }
+        }
         console.error({
           message: "Transaction failed during project creation",

28-36: Log hygiene: avoid noisy PII and prefer structured logger

Logging userId/workspaceId at error level for NOT_FOUND is excessive and may be PII‑sensitive. Use structured logger with sampling, or downgrade to warn; include a correlation/request id.

go/pkg/db/schema.sql (2)

297-308: Blocker: environments unique index too coarse for per‑project slugs

UNIQUE(workspace_id, slug) prevents multiple projects in a workspace from having the same environment slug ("production"/"preview"). This conflicts with create flow.

Action:

  • Change internal DB schema to UNIQUE(workspace_id, project_id, slug), run migrations, then regenerate this file.
  • Provide a backfill/migration plan to rebuild the unique index safely in prod.

316-323: Add index for projects.active_deployment_id

Lookups/join on active_deployment_id will benefit from an index when selecting projects by active deployment.

Proposal (generated file will reflect after changing internal schema):

CREATE INDEX `active_deployment_id_idx` ON `projects` (`active_deployment_id`);
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx (2)

36-41: Delete is a no‑op for UI state

Without invalidation or local state update, the row won’t disappear after “delete”. Until tRPC is wired, accept an onDelete callback to prune local state.

-type EnvVarRowProps = {
+type EnvVarRowProps = {
   envVar: EnvVar;
   projectId: string;
   getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined;
+  onDelete?: (id: string) => void;
 };
@@
-  const handleDelete = async () => {
+  const handleDelete = async () => {
     try {
@@
-      await new Promise((resolve) => setTimeout(resolve, 300));
+      await new Promise((resolve) => setTimeout(resolve, 300));
+      onDelete?.(envVar.id);

48-56: Tone: remove “stupid” from code comments

Keep comments professional. Rephrase or delete.

apps/dashboard/lib/collections/ratelimit_namespaces.ts (2)

24-31: Validate insert payload with zod (and avoid trusting UI state)

Parse just the name field before mutate to enforce schema constraints at the edge.

Apply:

   onInsert: async ({ transaction }) => {
-      const { changes: newNamespace } = transaction.mutations[0];
-      if (!newNamespace.name) {
+      const { changes: newNamespace } = transaction.mutations[0];
+      const { name } = z.object({ name: schema.shape.name }).parse(newNamespace);
-        throw new Error("Namespace name is required");
-      }
-
-      const mutation = trpcClient.ratelimit.namespace.create.mutate({ name: newNamespace.name });
+      const mutation = trpcClient.ratelimit.namespace.create.mutate({ name });

Consider also short‑circuiting if a namespace with the same name already exists locally via the unique "name" index for a faster UX.


47-55: Also validate update name

Mirror insert validation on update to prevent empty/too‑long names from slipping through.

-      const mutation = trpcClient.ratelimit.namespace.update.name.mutate({
-        namespaceId: original.id,
-        name: modified.name,
-      });
+      const { name } = z.object({ name: schema.shape.name }).parse(modified);
+      const mutation = trpcClient.ratelimit.namespace.update.name.mutate({
+        namespaceId: original.id,
+        name,
+      });
apps/dashboard/lib/collections/ratelimit_overrides.ts (3)

21-24: Noisy log in hot path

console.info on every fetch will spam logs. Guard for dev or remove.

-      console.info("DB fetching ratelimitOverrides");
+      if (process.env.NODE_ENV !== "production") {
+        console.info("DB fetching ratelimitOverrides");
+      }

27-41: Bug: schema.parse(changes) requires id on insert

changes likely lacks id; this will throw. Parse only insertable fields.

+const insertSchema = schema.omit({ id: true });
@@
-      const mutation = trpcClient.ratelimit.override.create.mutate(schema.parse(changes));
+      const mutation = trpcClient.ratelimit.override.create.mutate(insertSchema.parse(changes));

44-55: Validate update payload (limit/duration) before mutate

Pre-validate to catch bad values early.

-      const mutation = trpcClient.ratelimit.override.update.mutate({
-        id: original.id,
-        limit: modified.limit,
-        duration: modified.duration,
-      });
+      const payload = z
+        .object({
+          id: schema.shape.id,
+          limit: schema.shape.limit,       // consider .int().positive()
+          duration: schema.shape.duration, // consider .int().positive()
+        })
+        .parse({ id: original.id, limit: modified.limit, duration: modified.duration });
+      const mutation = trpcClient.ratelimit.override.update.mutate(payload);
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1)

83-86: Fix styling sentinel after changing default title

The “active” background now applies almost always. Compare against TITLE_EMPTY_DEFAULT instead of "Last 12 hours".

-            title ? "" : "opacity-50",
-            title !== "Last 12 hours" ? "bg-gray-4" : "",
+            title ? "" : "opacity-50",
+            title && title !== TITLE_EMPTY_DEFAULT ? "bg-gray-4" : "",
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx (2)

8-14: Prop type drift: projectId is required but unused

Either use it or remove to avoid confusion.

-type AddEnvVarRowProps = {
-  projectId: string;
-  getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined;
-  onCancel: () => void;
-};
+type AddEnvVarRowProps = {
+  getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined;
+  onCancel: () => void;
+};

54-61: Data freshness TODO blocks UX

No invalidation or collection update after save; list may remain stale. Wire up invalidate/refetch when backend is ready, or optimistically insert into local collection.

I can draft an optimistic update against the env vars collection and a follow‑up invalidation once the mutation is added.

apps/dashboard/app/(app)/projects/_components/create-project/create-project-dialog.tsx (1)

42-48: Git URL null-handling is fine; consider trimming and normalizing.

Optionally coerce whitespace-only to null to avoid sending empty strings.

Apply:

-        gitRepositoryUrl: values.gitRepositoryUrl ?? null,
+        gitRepositoryUrl:
+          (values.gitRepositoryUrl?.trim()?.length ?? 0) > 0
+            ? values.gitRepositoryUrl.trim()
+            : null,
apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (2)

13-20: Bug: resolver ignores validated input; likely undefined access of ctx.validatedQuery

tRPC resolvers receive input; using ctx.validatedQuery will return undefined unless middleware injects it. Use input.query.

Apply this diff:

 export const searchDeployments = t.procedure
   .use(requireUser)
   .use(requireWorkspace)
   .use(withLlmAccess())
   .input(z.object({ query: z.string() }))
-  .mutation(async ({ ctx }) => {
-    return await getStructuredSearchFromLLM(openai, ctx.validatedQuery);
-  });
+  .mutation(async ({ ctx, input }) => {
+    return getStructuredSearchFromLLM(openai, input.query);
+  });

17-17: Harden input: trim and require non-empty query

Prevents needless LLM calls with blank text.

-  .input(z.object({ query: z.string() }))
+  .input(z.object({ query: z.string().trim().min(1, "Query is required") }))
apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx (1)

64-69: Harden against undefined data

Avoid crashes if projects.data is temporarily undefined or errors out.

-            items={projects.data.map((project) => ({
+            items={(projects.data ?? []).map((project) => ({
               id: project.id,
               label: project.name,
               href: `/projects/${project.id}`,
             }))}
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx (1)

24-29: Confirm: deployment.id looks wrong for linking to a root key — change required

  • The action in apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx uses router.push(/settings/root-keys/${deployment.id}) but Deployment.id is a deployment identifier, not a RootKey id. Evidence: settings root-keys pages and links consistently use RootKey.id (e.g. apps/dashboard/app/(app)/settings/root-keys/... and other links like /settings/root-keys/${row.original.id} and vercel bindings use /settings/root-keys/${props.binding.resourceId}). The repo contains dedicated RootKey types and edit flows that expect a RootKey object/ID (see root-keys table actions which call onEditKey(rootKey) rather than navigate).
  • Fix: replace deployment.id with the correct root key identifier (e.g. deployment.rootKeyId / deployment.keyAuthId / the appropriate field on the Deployment object) or open the Root Key edit UI (call the same handler used by RootKeysTableActions) instead of navigating to /settings/root-keys/:id.
  • Location to change: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx — update the onClick to use the proper root-key id or remove the incorrect route.
apps/dashboard/app/(app)/projects/[projectId]/layout.tsx (1)

52-56: Use functional state update and expose pressed state.

Avoid stale closures and improve a11y.

- onClick={() => setIsDetailsOpen(!isDetailsOpen)}
+ onClick={() => setIsDetailsOpen((v) => !v)}
+ aria-pressed={isDetailsOpen}
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (2)

95-121: Shimmer animation likely broken due to styled-jsx scoping.

@keyframes inside <style jsx> gets scoped; the inline animation: "shimmer ..." won’t resolve to the hashed name. Make the keyframes global (or move animation to a class inside the same block).

-      {animated && (
-        <div
-          className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent w-[150%]"
-          style={{
-            animation: "shimmer 1.2s ease-in-out infinite",
-          }}
-        />
-      )}
+      {animated && (
+        <div
+          className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent w-[150%] animate-deploy-shimmer"
+        />
+      )}
       {animated && (
-        <style jsx>{`
+        <style jsx global>{`
           @keyframes shimmer {
             0% {
               transform: translateX(-100%);
             }
             100% {
               transform: translateX(100%);
             }
           }
+          .animate-deploy-shimmer {
+            animation: shimmer 1.2s ease-in-out infinite;
+          }
         `}</style>
       )}

80-83: Don’t throw on unknown status; degrade gracefully.

Throwing here crashes the client. Provide a fallback config.

-  if (!config) {
-    throw new Error(`Invalid deployment status: ${status}`);
-  }
+  const safe = config ?? {
+    icon: CircleHalfDottedClock,
+    label: String(status),
+    bgColor: "bg-grayA-3",
+    textColor: "text-grayA-11",
+    iconColor: "text-gray-11",
+  } as const;

Then use safe instead of config.

apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (4)

50-57: Remove or disable “View logs” until a real route exists.

Navigating to .../deployments/idk/logs is a broken UX.

-      onClick: () => {
-        //INFO: This will change soon
-        const fakeDeploymentId = "idk";
-        router.push(`/projects/${projectId}/deployments/${fakeDeploymentId}/logs`);
-      },
+      onClick: () => toast.info("Logs are coming soon"),
+      disabled: true,

60-67: Remove or disable “Project settings” placeholder.

Same issue: broken route with a fake ID.

-      onClick: () => {
-        //INFO: This will change soon
-        const fakeDeploymentId = "idk";
-        router.push(`/projects/${projectId}/deployments/${fakeDeploymentId}/settings`);
-      },
+      onClick: () => toast.info("Project settings are coming soon"),
+      disabled: true,

25-30: Disable or implement “Add favorite”.

Currently a no-op. Either wire it or mark disabled.

-      onClick: () => {},
+      disabled: true,

71-75: Delete project action has no UI; gate behind a confirm.

At least show a confirm modal or disable to avoid confusion.

apps/dashboard/lib/collections/index.ts (1)

26-34: Make reset resilient to partial failures.

One failing collection aborts the whole reset. Use allSettled and log failures.

-export async function reset() {
-  await Promise.all(
-    Object.values(collection).map(async (c) => {
-      await c.cleanup();
-      await c.preload();
-    }),
-  );
-}
+export async function reset() {
+  const ops = Object.values(collection).map(async (c) => {
+    try {
+      await c.cleanup();
+      await c.preload();
+    } catch (e) {
+      // eslint-disable-next-line no-console
+      console.error("collection reset failed", { name: c.name, error: e });
+    }
+  });
+  await Promise.allSettled(ops);
+}
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (1)

94-99: Badge text color is hardcoded to success

The class "text-successA-11" forces green text even for warning/error variants.

-          <Badge variant={statusConfig.variant} className="text-successA-11 font-medium">
+          <Badge variant={statusConfig.variant} className="font-medium">
📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10f390e and 324c314.

📒 Files selected for processing (55)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/deployment-status-filter.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx (11 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/hooks/use-deployments-list-query.ts (0 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (6 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/skeleton.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (12 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (7 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (8 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx (0 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx (3 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx (4 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/create-project-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/create-project.schema.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/hooks/use-projects-list-query.ts (0 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/index.tsx (3 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (4 hunks)
  • apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (2 hunks)
  • apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (1 hunks)
  • apps/dashboard/lib/collections/deployments.ts (1 hunks)
  • apps/dashboard/lib/collections/domains.ts (1 hunks)
  • apps/dashboard/lib/collections/environments.ts (1 hunks)
  • apps/dashboard/lib/collections/index.ts (1 hunks)
  • apps/dashboard/lib/collections/projects.ts (1 hunks)
  • apps/dashboard/lib/collections/ratelimit_namespaces.ts (1 hunks)
  • apps/dashboard/lib/collections/ratelimit_overrides.ts (1 hunks)
  • apps/dashboard/lib/shorten-id.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/active-deployment/getDetails.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/deployment/list.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/list.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/domains/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/environment/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/project/create.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/project/list.ts (1 hunks)
  • go/pkg/db/schema.sql (1 hunks)
  • internal/db/src/schema/projects.ts (1 hunks)
💤 Files with no reviewable changes (6)
  • apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/hooks/use-deployments-list-query.ts
  • apps/dashboard/app/(app)/projects/_components/list/hooks/use-projects-list-query.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/deployment/list.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/list.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/active-deployment/getDetails.ts
🧰 Additional context used
🧠 Learnings (14)
📓 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.
📚 Learning: 2025-07-25T19:09:43.284Z
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.

Applied to files:

  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx
  • apps/dashboard/lib/trpc/routers/environment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/lib/trpc/routers/domains/list.ts
  • apps/dashboard/lib/trpc/routers/project/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts
  • apps/dashboard/lib/collections/deployments.ts
  • apps/dashboard/lib/trpc/routers/deployment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
📚 Learning: 2025-08-31T19:30:38.171Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3853
File: apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts:75-87
Timestamp: 2025-08-31T19:30:38.171Z
Learning: In apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts, the getEnvs procedure currently uses mock data (VARIABLES) and ignores the projectId input parameter. This is intentional temporary behavior - the user ogzhanolguncu indicated they plan to hook this up to the database later.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx
  • apps/dashboard/lib/trpc/routers/environment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx
  • apps/dashboard/lib/trpc/routers/project/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx
📚 Learning: 2025-06-02T11:09:58.791Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx
📚 Learning: 2025-07-25T19:11:00.208Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/lib/trpc/routers/deployment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
📚 Learning: 2025-07-28T20:38:53.244Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
📚 Learning: 2025-08-22T12:50:07.024Z
Learnt from: perkinsjr
PR: unkeyed/unkey#3775
File: apps/dashboard/lib/trpc/routers/api/keys/query-overview-timeseries/index.ts:1-2
Timestamp: 2025-08-22T12:50:07.024Z
Learning: The team at Unkey is comfortable accepting TRPC schema imports from app route folders in server-side TRPC routers as technical debt, since they're planning to migrate away from TRPC to calling their v2 API directly, making these imports temporary and not worth refactoring.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/lib/trpc/routers/index.ts
📚 Learning: 2024-10-23T12:05:31.121Z
Learnt from: chronark
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/lib/collections/ratelimit_overrides.ts
  • apps/dashboard/lib/collections/ratelimit_namespaces.ts
📚 Learning: 2025-06-24T13:29:10.129Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx
📚 Learning: 2025-01-22T16:51:59.978Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx
📚 Learning: 2025-08-25T12:56:59.310Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3834
File: apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts:59-66
Timestamp: 2025-08-25T12:56:59.310Z
Learning: In the ratelimit namespace query system (apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts), the nameQuery filter is designed as an array for future extensibility to support multiple filters, but currently only the first filter (index 0) is processed. This is intentional future-proofing.

Applied to files:

  • apps/dashboard/lib/collections/ratelimit_namespaces.ts
📚 Learning: 2025-08-18T10:28:47.391Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3797
File: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/control-cloud/index.tsx:1-4
Timestamp: 2025-08-18T10:28:47.391Z
Learning: In Next.js App Router, components that use React hooks don't need their own "use client" directive if they are rendered within a client component that already has the directive. The client boundary propagates to child components.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
📚 Learning: 2025-07-28T20:36:36.865Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx
📚 Learning: 2025-09-12T17:57:12.128Z
Learnt from: mcstepp
PR: unkeyed/unkey#3952
File: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/rollback-dialog.tsx:111-116
Timestamp: 2025-09-12T17:57:12.128Z
Learning: In the Deployment type used across the dashboard deployment components, the source field is required and will always be present, so optional chaining (source?.branch) is not needed when accessing source.branch or source.gitSha.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
🧬 Code graph analysis (33)
apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (1)
apps/dashboard/lib/trpc/trpc.ts (1)
  • t (8-8)
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/lib/collections/projects.ts (2)
internal/db/src/schema/projects.ts (1)
  • projects (8-31)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx (1)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx (1)
  • DeploymentsList (48-439)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/skeleton.tsx (1)
internal/icons/src/icons/circle-check.tsx (1)
  • CircleCheck (16-40)
apps/dashboard/lib/trpc/routers/environment/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/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx (2)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (1)
  • createDetailSections (32-258)
apps/dashboard/lib/trpc/routers/domains/list.ts (2)
apps/dashboard/lib/trpc/trpc.ts (4)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
  • withRatelimit (122-138)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
apps/dashboard/lib/trpc/routers/project/list.ts (2)
apps/dashboard/lib/trpc/trpc.ts (4)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
  • withRatelimit (122-138)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx (3)
apps/dashboard/lib/collections/environments.ts (1)
  • environments (15-59)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/components/logs/checkbox/filter-checkbox.tsx (1)
  • FilterCheckbox (55-259)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1)
apps/dashboard/app/(app)/projects/[projectId]/deployments/hooks/use-filters.ts (1)
  • useFilters (33-127)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (1)
apps/dashboard/components/logs/table-action.popover.tsx (2)
  • TableActionPopover (29-148)
  • MenuItem (12-22)
apps/dashboard/lib/collections/environments.ts (1)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (3)
internal/icons/src/icons/nut.tsx (1)
  • Nut (15-43)
internal/icons/src/icons/half-dotted-circle-play.tsx (1)
  • HalfDottedCirclePlay (15-86)
internal/icons/src/icons/arrow-dot-anti-clockwise.tsx (1)
  • ArrowDotAntiClockwise (15-55)
apps/dashboard/lib/collections/domains.ts (1)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (3)
apps/dashboard/lib/collections/deployments.ts (1)
  • deployments (40-84)
internal/db/src/schema/deployments.ts (1)
  • deployments (9-52)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts (2)
apps/dashboard/lib/collections/deployments.ts (1)
  • Deployment (38-38)
apps/dashboard/lib/collections/index.ts (1)
  • Deployment (10-10)
apps/dashboard/lib/collections/deployments.ts (1)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/lib/trpc/routers/deployment/list.ts (1)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (4)
apps/dashboard/lib/collections/deployments.ts (1)
  • deployments (40-84)
internal/db/src/schema/deployments.ts (1)
  • deployments (9-52)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/lib/trpc/routers/index.ts (1)
  • router (124-333)
apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (3)
internal/icons/src/icons/link-4.tsx (1)
  • Link4 (16-45)
internal/icons/src/icons/share-up-right.tsx (1)
  • ShareUpRight (15-56)
internal/icons/src/icons/circle-check.tsx (1)
  • CircleCheck (16-40)
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (2)
apps/dashboard/lib/collections/deployments.ts (1)
  • Deployment (38-38)
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/detail-section.tsx (1)
  • DetailSection (33-55)
apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx (3)
apps/dashboard/lib/collections/projects.ts (1)
  • projects (19-64)
internal/db/src/schema/projects.ts (1)
  • projects (8-31)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (2)
apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-api-navigation.tsx (1)
  • useApiNavigation (11-119)
apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (1)
  • useProjectNavigation (8-53)
apps/dashboard/app/(app)/projects/[projectId]/page.tsx (5)
apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx (1)
  • useProjectLayout (12-18)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
internal/db/src/schema/projects.ts (1)
  • projects (8-31)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (1)
  • ActiveDeploymentCard (54-250)
apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (1)
  • DomainRow (8-22)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (6)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1)
  • useDeploymentLogs (43-162)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/skeleton.tsx (1)
  • ActiveDeploymentCardSkeleton (6-66)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/card.tsx (1)
  • Card (4-12)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/status-indicator.tsx (1)
  • StatusIndicator (4-32)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/info-chip.tsx (1)
  • InfoChip (8-13)
apps/dashboard/lib/trpc/routers/index.ts (10)
apps/dashboard/lib/trpc/trpc.ts (1)
  • t (8-8)
apps/dashboard/lib/trpc/routers/project/list.ts (1)
  • listProjects (5-30)
apps/dashboard/lib/trpc/routers/project/create.ts (1)
  • createProject (8-174)
apps/dashboard/lib/trpc/routers/domains/list.ts (1)
  • listDomains (5-28)
apps/dashboard/lib/trpc/routers/deployment/list.ts (1)
  • listDeployments (5-37)
apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (1)
  • searchDeployments (13-20)
apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1)
  • getOpenApiDiff (7-196)
apps/dashboard/lib/trpc/routers/deployment/buildLogs.ts (1)
  • getDeploymentBuildLogs (217-230)
apps/dashboard/lib/trpc/routers/environment/list.ts (1)
  • listEnvironments (5-24)
apps/dashboard/lib/trpc/routers/project/envs/list.ts (1)
  • getEnvs (68-80)
apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (2)
apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (1)
  • NavItem (16-29)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/_components/list/index.tsx (5)
apps/dashboard/lib/collections/projects.ts (1)
  • projects (19-64)
internal/db/src/schema/projects.ts (1)
  • projects (8-31)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/_components/list/projects-card.tsx (1)
  • ProjectCard (21-111)
apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (1)
  • ProjectActions (15-20)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx (7)
apps/dashboard/app/(app)/projects/[projectId]/deployments/hooks/use-filters.ts (1)
  • useFilters (33-127)
apps/dashboard/lib/collections/deployments.ts (2)
  • deployments (40-84)
  • Deployment (38-38)
apps/dashboard/lib/collections/environments.ts (2)
  • environments (15-59)
  • Environment (13-13)
apps/dashboard/lib/collections/index.ts (3)
  • collection (17-24)
  • Deployment (10-10)
  • Environment (15-15)
apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (1)
  • DeploymentListFilterField (117-117)
apps/dashboard/components/virtual-table/types.ts (1)
  • Column (13-24)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts (1)
  • getRowClassName (38-52)
⏰ 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). (5)
  • GitHub Check: Test Packages / Test
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Analyze (javascript-typescript)

Comment on lines +327 to 333
environment: t.router({
list: listEnvironments,
}),
environmentVariables: t.router({
list: getEnvs,
}),
deployment: deploymentRouter,
});
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 and environmentVariables routers are fine; consider naming clarity

Optional: environmentVariables might be clearer as projectEnvVars if it’s project-scoped.

🤖 Prompt for AI Agents
In apps/dashboard/lib/trpc/routers/index.ts around lines 327 to 333, the router
key environmentVariables is potentially ambiguous; rename it to projectEnvVars
(or another clearer name) by updating the router declaration key and then update
all places that import or reference this router (client-side trpc call sites,
type references, tests and any string-based keys) to use the new name to keep
names consistent across the codebase.

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.

Review continued from previous batch...

icon: <CodeCommit className="size-[14px] text-gray-12" size="md-regular" />,
label: "Commit",
content: <span className="text-gray-12 font-medium">{details.commit}</span>,
content: <span className="text-gray-12 font-medium">{details.gitCommitSha}</span>,
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)

Shorten commit SHA to 7 chars for readability

Matches usage in other components.

-        content: <span className="text-gray-12 font-medium">{details.gitCommitSha}</span>,
+        content: <span className="text-gray-12 font-medium">{details.gitCommitSha?.slice(0, 7)}</span>,
📝 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
content: <span className="text-gray-12 font-medium">{details.gitCommitSha}</span>,
content: <span className="text-gray-12 font-medium">{details.gitCommitSha?.slice(0, 7)}</span>,
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx
around line 53, the commit SHA is rendered in full; update the content to
display a shortened 7-character SHA for readability (matching other components).
Use a safe slice (e.g. details.gitCommitSha?.slice(0, 7) or substring with a
fallback) so undefined/null values don't error, and keep the surrounding span
and classes unchanged.

Comment on lines 69 to 75
<img
src={details.author.avatar}
alt={details.author.name}
src={details.gitCommitAuthorAvatarUrl ?? ""}
alt={details.gitCommitAuthorUsername ?? ""}
className="rounded-full size-5"
/>
<span className="font-medium text-grayA-12">{details.author.name}</span>
<span className="font-medium text-grayA-12">{details.gitCommitAuthorUsername}</span>
</div>
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

Avoid empty img src; provide a proper fallback avatar

An empty string src can trigger unintended requests to the current page and 404s. Render the avatar only when a valid URL exists, otherwise show a placeholder.

-            <img
-              src={details.gitCommitAuthorAvatarUrl ?? ""}
-              alt={details.gitCommitAuthorUsername ?? ""}
-              className="rounded-full size-5"
-            />
+            {details.gitCommitAuthorAvatarUrl ? (
+              <img
+                src={details.gitCommitAuthorAvatarUrl}
+                alt={`${details.gitCommitAuthorUsername ?? "Author"} avatar`}
+                className="rounded-full size-5"
+              />
+            ) : (
+              <div className="rounded-full size-5 bg-grayA-3" aria-label="No avatar" />
+            )}
📝 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
<img
src={details.author.avatar}
alt={details.author.name}
src={details.gitCommitAuthorAvatarUrl ?? ""}
alt={details.gitCommitAuthorUsername ?? ""}
className="rounded-full size-5"
/>
<span className="font-medium text-grayA-12">{details.author.name}</span>
<span className="font-medium text-grayA-12">{details.gitCommitAuthorUsername}</span>
</div>
{details.gitCommitAuthorAvatarUrl ? (
<img
src={details.gitCommitAuthorAvatarUrl}
alt={`${details.gitCommitAuthorUsername ?? "Author"} avatar`}
className="rounded-full size-5"
/>
) : (
<div className="rounded-full size-5 bg-grayA-3" aria-label="No avatar" />
)}
<span className="font-medium text-grayA-12">{details.gitCommitAuthorUsername}</span>
</div>
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx
around lines 69 to 75, the img uses an empty-string src when
details.gitCommitAuthorAvatarUrl is falsy which can trigger unwanted requests;
change the JSX to conditionally render the <img> only when
details.gitCommitAuthorAvatarUrl is a non-empty string and render a fallback
placeholder (e.g., a styled div with initials or an SVG/avatar component)
otherwise, and ensure the alt text still reflects the author username in both
branches.

Comment on lines +145 to 147
<span className="text-gray-12 font-medium">20GB</span>
mb
</div>
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

Fix unit mismatch: "20GB" followed by "mb"

Currently renders as “20GBmb”. Pick one unit and format consistently.

-            <span className="text-gray-12 font-medium">20GB</span>
-            mb
+            <span className="text-gray-12 font-medium">20</span>&nbsp;GB
📝 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
<span className="text-gray-12 font-medium">20GB</span>
mb
</div>
<span className="text-gray-12 font-medium">20</span>&nbsp;GB
</div>
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx
around lines 145-147, the UI currently renders "20GB" followed by "mb" producing
"20GBmb"; pick a single unit and make the text consistent (e.g., "20 GB" or "20
MB"), remove the duplicate unit, ensure correct casing (MB/GB) and include a
space between the number and unit, and update the JSX accordingly so only the
chosen, correctly formatted unit is rendered.

Comment on lines +21 to +27
.from({ deployment: collection.deployments })
.where(({ deployment }) => eq(deployment.projectId, params?.projectId))
.join({ environment: collection.environments }, ({ environment, deployment }) =>
eq(environment.id, deployment.environmentId),
)
.orderBy(({ deployment }) => deployment.createdAt, "desc")
.limit(100),
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 the already-derived projectId instead of params in the query

Avoid re-reading params; keeps types stable.

-      .where(({ deployment }) => eq(deployment.projectId, params?.projectId))
+      .where(({ deployment }) => eq(deployment.projectId, projectId))
📝 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
.from({ deployment: collection.deployments })
.where(({ deployment }) => eq(deployment.projectId, params?.projectId))
.join({ environment: collection.environments }, ({ environment, deployment }) =>
eq(environment.id, deployment.environmentId),
)
.orderBy(({ deployment }) => deployment.createdAt, "desc")
.limit(100),
.from({ deployment: collection.deployments })
.where(({ deployment }) => eq(deployment.projectId, projectId))
.join({ environment: collection.environments }, ({ environment, deployment }) =>
eq(environment.id, deployment.environmentId),
)
.orderBy(({ deployment }) => deployment.createdAt, "desc")
.limit(100),
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx around lines 21
to 27, the query is using params?.projectId directly which re-reads route params
and can destabilize types; replace params?.projectId with the already-derived
projectId variable (the one parsed earlier in the file) in the where clause so
the query uses the single canonical projectId value and keeps types stable.

Comment on lines +36 to +38
const sortedDeployments = deployments.data.sort(
(a, b) => b.deployment.createdAt - a.deployment.createdAt,
);
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)

Don’t mutate query results; you already sort in SQL

Array.sort mutates and is redundant after orderBy. Also guard for undefined.

-  const sortedDeployments = deployments.data.sort(
-    (a, b) => b.deployment.createdAt - a.deployment.createdAt,
-  );
+  const sortedDeployments = (deployments.data ?? []);
📝 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 sortedDeployments = deployments.data.sort(
(a, b) => b.deployment.createdAt - a.deployment.createdAt,
);
const sortedDeployments = (deployments.data ?? []);
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx around lines
36–38, the code calls deployments.data.sort(...) which mutates the query result
and is redundant because the SQL already orders by createdAt; instead, avoid
mutating the original array by either removing the sort entirely and using
deployments?.data (or deployments.data ?? []) directly, or if a client-side sort
is truly needed create a shallow copy first (e.g., const sortedDeployments =
(deployments?.data ?? []).slice().sort(...)). Also guard against undefined by
using deployments?.data or a fallback empty array before operating on it.

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: 53

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (21)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx (3)

24-26: Avoid stale closures when updating filters.

filters is captured at render-time; if state changes before onSuccess runs, you can overwrite newer user changes. Use a functional updater.

-      const transformedFilters = transformStructuredOutputToFilters(data, filters);
-      updateFilters(transformedFilters);
+      updateFilters((prev) => transformStructuredOutputToFilters(data, prev));

If updateFilters does not accept an updater function, confirm its API and we can adjust the hook to expose one.


27-39: Don’t surface raw error.message to end users.

TRPC/LLM errors can leak internals. Show a stable message and log the details elsewhere.

-      const errorMessage = `Unable to process your search request${
-        error.message ? `: ${error.message}` : "."
-      } Please try again or refine your search criteria.`;
+      const errorMessage =
+        "Unable to process your search request. Please try again or refine your search criteria.";
+      // Optionally log: Sentry.captureException(error)

52-56: Guard against empty/whitespace queries before calling the LLM.

Saves an unnecessary network/mutation call and avoids noisy errors.

-      onSearch={(query) =>
-        queryLLMForStructuredOutput.mutateAsync({
-          query,
-        })
-      }
+      onSearch={(query) => {
+        const q = query?.trim();
+        if (!q) {
+          toast.error("Please enter a search query.", {
+            duration: 4000,
+            position: "top-right",
+          });
+          return;
+        }
+        return queryLLMForStructuredOutput.mutateAsync({ query: q });
+      }}
apps/dashboard/lib/trpc/routers/project/create.ts (3)

102-110: Environment inserts violate NOT NULL created_at; set lifecycle timestamps.

environments.created_at is NOT NULL in schema; these inserts omit it and will fail.

Apply:

-            await tx.insert(schema.environments).values({
+            await tx.insert(schema.environments).values({
               id: environmentId,
               workspaceId,
               projectId,
               slug: slug,
+              createdAt: now,
+              updatedAt: now,
             });

132-144: Map duplicate‑key to TRPC CONFLICT (eliminate TOCTOU race).

Slug pre‑check can race; rely on the unique index and translate 1062 to CONFLICT so UX messaging remains correct.

-      } catch (txErr) {
+      } catch (txErr: unknown) {
         console.error({
           message: "Transaction failed during project creation",
           userId,
           workspaceId,
           projectId,
           projectSlug: input.slug,
-          error: txErr instanceof Error ? txErr.message : String(txErr),
+          error: txErr instanceof Error ? txErr.message : String(txErr),
           stack: txErr instanceof Error ? txErr.stack : undefined,
         });
-
-        throw txErr; // Re-throw to be caught by outer catch
+        // Duplicate key (e.g., workspace_slug_idx) -> CONFLICT
+        const msg = String(txErr?.toString?.() ?? txErr);
+        if (msg.includes("Duplicate entry") || msg.includes("ER_DUP_ENTRY") || msg.includes("workspace_slug_idx")) {
+          throw new TRPCError({ code: "CONFLICT", message: `A project with slug "${input.slug}" already exists in this workspace` });
+        }
+        throw txErr; // Let outer catch handle generic cases

102-130: Workspace‑level UNIQUE(env.slug) will block multiple projects

Confirmed: go/pkg/db/schema.sql defines CONSTRAINT environments_workspace_id_slug_idx UNIQUE(workspace_id,slug), so inserting "production"/"preview" per project will fail on the second project.

  • Preferred: change the unique index to (project_id, slug) and regenerate schema/migrations.
  • Interim: namespace slugs per project (e.g., production-${input.slug}) — breaks expected slugs elsewhere.
  • Short-term mitigation: add a server-side guard to check for existing (workspace_id, slug) before insert and return a clear error or skip creation until migration.

Locations: apps/dashboard/lib/trpc/routers/project/create.ts (insertion loop, ~lines 102–130); go/pkg/db/schema.sql (CONSTRAINT environments_workspace_id_slug_idx UNIQUE(workspace_id,slug) around line ~307).

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

13-20: Fix: use validated input (input.query) — ctx.validatedQuery is undefined.

TRPC passes validated input as input; current code will pass undefined and break the LLM call.

File: apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts

-export const searchDeployments = t.procedure
+export const searchDeployments = t.procedure
  .use(requireUser)
  .use(requireWorkspace)
  .use(withLlmAccess())
  .input(z.object({ query: z.string() }))
-  .mutation(async ({ ctx }) => {
-    return await getStructuredSearchFromLLM(openai, ctx.validatedQuery);
+  .mutation(async ({ input }) => {
+    return await getStructuredSearchFromLLM(openai, input.query);
   });
apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (2)

53-54: Incomplete status case handling in getColorClass.

The function references "completed" status which doesn't exist in GROUPED_DEPLOYMENT_STATUSES. This appears to be leftover from the old status model.

Apply this diff to fix the status check:

     getColorClass: (value) => {
-      if (value === "completed") {
+      if (value === "ready") {
         return "bg-success-9";
       }

144-146: Consider memoizing the parseAsAllOperatorsFilterArray.

Since allOperators is a constant, this parser could be memoized to avoid recreation on every module load.

+import { useMemo } from "react";
+
-export const parseAsAllOperatorsFilterArray = parseAsFilterValueArray<DeploymentListFilterOperator>(
-  [...allOperators],
-);
+export const parseAsAllOperatorsFilterArray = useMemo(
+  () => parseAsFilterValueArray<DeploymentListFilterOperator>([...allOperators]),
+  []
+);
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1)

58-68: Consider handling undefined timestamps gracefully.

If a log entry has an invalid timestamp, the format function will throw an error, causing the entire component to crash.

Apply defensive programming:

     return logsData.logs.map((log) => ({
-      timestamp: format(new Date(log.timestamp), "HH:mm:ss.SSS"),
+      timestamp: log.timestamp 
+        ? format(new Date(log.timestamp), "HH:mm:ss.SSS")
+        : "Unknown",
       level: log.level,
       message: log.message,
     }));
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx (1)

81-87: Ensure React event types are available.

If react types aren’t globally included, add a type-only import to avoid TS errors with React.KeyboardEvent.

Outside-range addition:

import type React from "react";
apps/dashboard/lib/collections/ratelimit_overrides.ts (2)

50-54: Toast error handler: surface server message safely

The error branch returns a string in update/delete but a structured object in create. Consider consistent structured errors for all toasts.


19-25: Scope the queryKey by workspace to avoid cross‑tenant cache bleed

queryClient is global (apps/dashboard/lib/collections/client.ts: export const queryClient = new QueryClient()) and ratelimit_overrides uses a static key—this will reuse data across workspaces. Change in apps/dashboard/lib/collections/ratelimit_overrides.ts:

-    queryKey: ["ratelimitOverrides"],
+    queryKey: ["ratelimitOverrides", /* workspaceId */],

If workspaceId isn’t available here, thread it via a collection factory or read it from the workspace context when creating the collection.

apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (1)

196-198: Use a stable unique key for list items

Keying by label risks collisions. Prefer href.

-                key={item.label as string}
+                key={item.href}
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (2)

83-86: Fix highlight logic to align with new empty title

Condition still checks against "Last 12 hours". It should use TITLE_EMPTY_DEFAULT to avoid false highlights.

-            title !== "Last 12 hours" ? "bg-gray-4" : "",
+            title && title !== TITLE_EMPTY_DEFAULT ? "bg-gray-4" : "",

90-91: Button disabled state is effectively unreachable

title is always a non-empty string now; disabled={!title} won’t trigger. Remove or reconsider if you plan a null title.

apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx (1)

8-12: Prop contract drifts: projectId declared but unused

Either use projectId for the upcoming mutation or remove it from the props to avoid confusion.

-type AddEnvVarRowProps = {
-  projectId: string;
-  getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined;
-  onCancel: () => void;
-};
+type AddEnvVarRowProps = {
+  getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined;
+  onCancel: () => void;
+};
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx (1)

94-141: Hard‑coded domain metadata

Links/labels are static placeholders; wire them to domains collection to avoid confusion.

I can hook this to collection.domains filtered by projectId and render the count in the +2 badge. Proceed?

apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (1)

103-107: Tailwind class z-5 may not exist

Tailwind’s default scale doesn’t include z-5; use arbitrary value.

-      <Icon
-        size={config.icon === Nut ? "md-bold" : "md-regular"}
-        className={cn(iconColor, animated && "relative z-5")}
-      />
+      <Icon
+        size={config.icon === Nut ? "md-bold" : "md-regular"}
+        className={cn(iconColor, animated && "relative z-[1]")}
+      />
-      <span className={cn(textColor, "text-xs", animated && "relative z-5")}>{label}</span>
+      <span className={cn(textColor, "text-xs", animated && "relative z-[1]")}>{label}</span>

Also applies to: 107-107

apps/dashboard/lib/collections/index.ts (1)

26-34: Reset looks fine; watch for all‑or‑nothing failure

Promise.all aborts on first rejection. If partial reset is acceptable, switch to allSettled and log failures; otherwise keep as is.

I can provide an allSettled variant with structured logging if desired.

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

281-296: Inefficient deployment ID display in preview.

The preview section shows the full deployment ID which may be too long. Consider showing a shortened version or more meaningful information.

                     From: {(() => {
                       const deployment = deployments.data.find(
                         (d) => d.deployment.id === selectedFromDeployment,
                       );
-                      return deployment ? deployment.deployment.id : "Unknown";
+                      const sha = deployment?.deployment.gitCommitSha?.substring(0, 7) || deployment?.deployment.id.substring(0, 7);
+                      const branch = deployment?.deployment.gitBranch || "unknown";
+                      return deployment ? `${branch}:${sha}` : "Unknown";
                     })()}
♻️ Duplicate comments (1)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1)

14-25: Nice: title resets when time filters are cleared

This addresses the previously raised UX issue about stale titles after clearing filters.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10f390e and 324c314.

📒 Files selected for processing (55)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/deployment-status-filter.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx (11 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/hooks/use-deployments-list-query.ts (0 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (6 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/skeleton.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (12 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (7 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (8 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx (0 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx (3 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx (4 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/create-project-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/create-project.schema.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/hooks/use-projects-list-query.ts (0 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/index.tsx (3 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (4 hunks)
  • apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (2 hunks)
  • apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (1 hunks)
  • apps/dashboard/lib/collections/deployments.ts (1 hunks)
  • apps/dashboard/lib/collections/domains.ts (1 hunks)
  • apps/dashboard/lib/collections/environments.ts (1 hunks)
  • apps/dashboard/lib/collections/index.ts (1 hunks)
  • apps/dashboard/lib/collections/projects.ts (1 hunks)
  • apps/dashboard/lib/collections/ratelimit_namespaces.ts (1 hunks)
  • apps/dashboard/lib/collections/ratelimit_overrides.ts (1 hunks)
  • apps/dashboard/lib/shorten-id.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/active-deployment/getDetails.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/deployment/list.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/list.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/domains/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/environment/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/project/create.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/project/list.ts (1 hunks)
  • go/pkg/db/schema.sql (1 hunks)
  • internal/db/src/schema/projects.ts (1 hunks)
💤 Files with no reviewable changes (6)
  • apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx
  • apps/dashboard/app/(app)/projects/_components/list/hooks/use-projects-list-query.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/hooks/use-deployments-list-query.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/active-deployment/getDetails.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/deployment/list.ts
🧰 Additional context used
🧠 Learnings (15)
📓 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.
📚 Learning: 2025-08-31T19:30:38.171Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3853
File: apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts:75-87
Timestamp: 2025-08-31T19:30:38.171Z
Learning: In apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts, the getEnvs procedure currently uses mock data (VARIABLES) and ignores the projectId input parameter. This is intentional temporary behavior - the user ogzhanolguncu indicated they plan to hook this up to the database later.

Applied to files:

  • apps/dashboard/lib/trpc/routers/environment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx
📚 Learning: 2025-07-25T19:09:43.284Z
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.

Applied to files:

  • apps/dashboard/lib/trpc/routers/environment/list.ts
  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts
  • apps/dashboard/lib/collections/deployments.ts
  • apps/dashboard/lib/trpc/routers/domains/list.ts
  • internal/db/src/schema/projects.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx
  • apps/dashboard/lib/trpc/routers/project/create.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx
  • apps/dashboard/lib/trpc/routers/project/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/lib/trpc/routers/deployment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
  • apps/dashboard/lib/trpc/routers/index.ts
📚 Learning: 2025-07-25T19:11:00.208Z
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.

Applied to files:

  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx
  • apps/dashboard/lib/trpc/routers/project/create.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/lib/trpc/routers/deployment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/lib/trpc/routers/index.ts
📚 Learning: 2025-06-24T13:29:10.129Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx
📚 Learning: 2025-06-02T11:09:58.791Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx
📚 Learning: 2025-07-28T20:36:36.865Z
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.

Applied to files:

  • apps/dashboard/lib/trpc/routers/project/create.ts
📚 Learning: 2024-10-23T12:05:31.121Z
Learnt from: chronark
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/lib/collections/ratelimit_overrides.ts
📚 Learning: 2025-07-28T20:38:53.244Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/app/(app)/projects/_components/list/index.tsx
📚 Learning: 2025-08-22T12:50:07.024Z
Learnt from: perkinsjr
PR: unkeyed/unkey#3775
File: apps/dashboard/lib/trpc/routers/api/keys/query-overview-timeseries/index.ts:1-2
Timestamp: 2025-08-22T12:50:07.024Z
Learning: The team at Unkey is comfortable accepting TRPC schema imports from app route folders in server-side TRPC routers as technical debt, since they're planning to migrate away from TRPC to calling their v2 API directly, making these imports temporary and not worth refactoring.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
📚 Learning: 2025-08-25T12:56:59.310Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3834
File: apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts:59-66
Timestamp: 2025-08-25T12:56:59.310Z
Learning: In the ratelimit namespace query system (apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts), the nameQuery filter is designed as an array for future extensibility to support multiple filters, but currently only the first filter (index 0) is processed. This is intentional future-proofing.

Applied to files:

  • apps/dashboard/lib/collections/ratelimit_namespaces.ts
📚 Learning: 2025-08-18T10:28:47.391Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3797
File: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/control-cloud/index.tsx:1-4
Timestamp: 2025-08-18T10:28:47.391Z
Learning: In Next.js App Router, components that use React hooks don't need their own "use client" directive if they are rendered within a client component that already has the directive. The client boundary propagates to child components.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
📚 Learning: 2025-01-22T16:51:59.978Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx
📚 Learning: 2025-08-25T13:46:08.303Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3834
File: apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-datetime/index.tsx:1-1
Timestamp: 2025-08-25T13:46:08.303Z
Learning: The NamespaceListDateTime component in apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-datetime/index.tsx is intentionally designed to use the overview hook (useFilters from @/app/(app)/ratelimits/[namespaceId]/_overview/hooks/use-filters) rather than the namespace list hook, as clarified by ogzhanolguncu. This coupling is by design, not an architectural issue.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx
📚 Learning: 2025-09-12T17:57:12.128Z
Learnt from: mcstepp
PR: unkeyed/unkey#3952
File: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/rollback-dialog.tsx:111-116
Timestamp: 2025-09-12T17:57:12.128Z
Learning: In the Deployment type used across the dashboard deployment components, the source field is required and will always be present, so optional chaining (source?.branch) is not needed when accessing source.branch or source.gitSha.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
🧬 Code graph analysis (33)
apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/lib/trpc/routers/environment/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/llm-search/index.ts (1)
apps/dashboard/lib/trpc/trpc.ts (1)
  • t (8-8)
apps/dashboard/lib/collections/domains.ts (1)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx (4)
apps/dashboard/app/(app)/projects/[projectId]/deployments/hooks/use-filters.ts (1)
  • useFilters (33-127)
apps/dashboard/lib/collections/environments.ts (1)
  • environments (15-59)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/components/logs/checkbox/filter-checkbox.tsx (1)
  • FilterCheckbox (55-259)
apps/dashboard/lib/collections/deployments.ts (1)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/lib/trpc/routers/domains/list.ts (2)
apps/dashboard/lib/trpc/trpc.ts (4)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
  • withRatelimit (122-138)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx (1)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx (1)
  • DeploymentsList (48-439)
apps/dashboard/lib/trpc/routers/project/list.ts (2)
apps/dashboard/lib/trpc/trpc.ts (4)
  • t (8-8)
  • requireUser (10-21)
  • requireWorkspace (23-36)
  • withRatelimit (122-138)
apps/dashboard/lib/db.ts (1)
  • db (5-26)
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx (2)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (1)
  • createDetailSections (32-258)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/skeleton.tsx (1)
internal/icons/src/icons/circle-check.tsx (1)
  • CircleCheck (16-40)
apps/dashboard/lib/collections/ratelimit_namespaces.ts (2)
apps/dashboard/lib/collections/index.ts (1)
  • RatelimitNamespace (13-13)
internal/db/src/index.ts (1)
  • schema (8-8)
apps/dashboard/lib/collections/projects.ts (2)
internal/db/src/schema/projects.ts (1)
  • projects (8-31)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (6)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1)
  • useDeploymentLogs (43-162)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/skeleton.tsx (1)
  • ActiveDeploymentCardSkeleton (6-66)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/card.tsx (1)
  • Card (4-12)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/status-indicator.tsx (1)
  • StatusIndicator (4-32)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/info-chip.tsx (1)
  • InfoChip (8-13)
apps/dashboard/lib/collections/environments.ts (1)
apps/dashboard/lib/collections/client.ts (1)
  • trpcClient (12-25)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1)
apps/dashboard/lib/trpc/server.ts (1)
  • trpc (7-14)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1)
apps/dashboard/app/(app)/projects/[projectId]/deployments/hooks/use-filters.ts (1)
  • useFilters (33-127)
apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx (3)
apps/dashboard/lib/collections/projects.ts (1)
  • projects (19-64)
internal/db/src/schema/projects.ts (1)
  • projects (8-31)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (3)
internal/icons/src/icons/link-4.tsx (1)
  • Link4 (16-45)
internal/icons/src/icons/share-up-right.tsx (1)
  • ShareUpRight (15-56)
internal/icons/src/icons/circle-check.tsx (1)
  • CircleCheck (16-40)
apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (4)
apps/dashboard/lib/collections/deployments.ts (1)
  • deployments (40-84)
internal/db/src/schema/deployments.ts (1)
  • deployments (9-52)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/lib/trpc/routers/index.ts (1)
  • router (124-333)
apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (2)
apps/dashboard/components/navigation/sidebar/workspace-navigations.tsx (1)
  • NavItem (16-29)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (3)
apps/dashboard/lib/collections/deployments.ts (1)
  • deployments (40-84)
internal/db/src/schema/deployments.ts (1)
  • deployments (9-52)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts (2)
apps/dashboard/lib/collections/deployments.ts (1)
  • Deployment (38-38)
apps/dashboard/lib/collections/index.ts (1)
  • Deployment (10-10)
apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (3)
apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-api-navigation.tsx (1)
  • useApiNavigation (11-119)
apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-ratelimit-navigation.tsx (1)
  • useRatelimitNavigation (15-95)
apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (1)
  • useProjectNavigation (8-53)
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (2)
apps/dashboard/lib/collections/deployments.ts (1)
  • Deployment (38-38)
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/detail-section.tsx (1)
  • DetailSection (33-55)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx (8)
apps/dashboard/app/(app)/projects/[projectId]/deployments/hooks/use-filters.ts (1)
  • useFilters (33-127)
apps/dashboard/lib/collections/deployments.ts (2)
  • deployments (40-84)
  • Deployment (38-38)
apps/dashboard/lib/collections/environments.ts (2)
  • environments (15-59)
  • Environment (13-13)
apps/dashboard/lib/collections/index.ts (3)
  • collection (17-24)
  • Deployment (10-10)
  • Environment (15-15)
apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (1)
  • DeploymentListFilterField (117-117)
apps/dashboard/components/virtual-table/types.ts (1)
  • Column (13-24)
apps/dashboard/components/virtual-table/index.tsx (1)
  • VirtualTable (38-404)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts (1)
  • getRowClassName (38-52)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (3)
internal/icons/src/icons/nut.tsx (1)
  • Nut (15-43)
internal/icons/src/icons/half-dotted-circle-play.tsx (1)
  • HalfDottedCirclePlay (15-86)
internal/icons/src/icons/arrow-dot-anti-clockwise.tsx (1)
  • ArrowDotAntiClockwise (15-55)
apps/dashboard/app/(app)/projects/[projectId]/page.tsx (7)
apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx (1)
  • useProjectLayout (12-18)
apps/dashboard/lib/collections/domains.ts (1)
  • domains (16-60)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/lib/collections/projects.ts (1)
  • projects (19-64)
internal/db/src/schema/projects.ts (1)
  • projects (8-31)
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (1)
  • ActiveDeploymentCard (54-250)
apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (1)
  • DomainRow (8-22)
apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (1)
apps/dashboard/components/logs/table-action.popover.tsx (2)
  • TableActionPopover (29-148)
  • MenuItem (12-22)
apps/dashboard/app/(app)/projects/_components/list/index.tsx (4)
apps/dashboard/lib/collections/projects.ts (1)
  • projects (19-64)
apps/dashboard/lib/collections/index.ts (1)
  • collection (17-24)
apps/dashboard/app/(app)/projects/_components/list/projects-card.tsx (1)
  • ProjectCard (21-111)
apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (1)
  • ProjectActions (15-20)
apps/dashboard/lib/trpc/routers/index.ts (10)
apps/dashboard/lib/trpc/trpc.ts (1)
  • t (8-8)
apps/dashboard/lib/trpc/routers/project/list.ts (1)
  • listProjects (5-30)
apps/dashboard/lib/trpc/routers/project/create.ts (1)
  • createProject (8-174)
apps/dashboard/lib/trpc/routers/domains/list.ts (1)
  • listDomains (5-28)
apps/dashboard/lib/trpc/routers/deployment/list.ts (1)
  • listDeployments (5-37)
apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (1)
  • searchDeployments (13-20)
apps/dashboard/lib/trpc/routers/deployment/getOpenApiDiff.ts (1)
  • getOpenApiDiff (7-196)
apps/dashboard/lib/trpc/routers/deployment/buildLogs.ts (1)
  • getDeploymentBuildLogs (217-230)
apps/dashboard/lib/trpc/routers/environment/list.ts (1)
  • listEnvironments (5-24)
apps/dashboard/lib/trpc/routers/project/envs/list.ts (1)
  • getEnvs (68-80)

Comment on lines +47 to 48
gitRepositoryUrl: values.gitRepositoryUrl ?? null,
});
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)

Normalize/trim Git URL; coerce empty to null.

Avoid storing whitespace. Trim client‑side before sending.

-        gitRepositoryUrl: values.gitRepositoryUrl ?? null,
+        gitRepositoryUrl: values.gitRepositoryUrl?.trim()
+          ? values.gitRepositoryUrl.trim()
+          : null,
📝 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
gitRepositoryUrl: values.gitRepositoryUrl ?? null,
});
gitRepositoryUrl: values.gitRepositoryUrl?.trim()
? values.gitRepositoryUrl.trim()
: null,
});
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/_components/create-project/create-project-dialog.tsx
around lines 47-48, the gitRepositoryUrl is passed through as-is which allows
leading/trailing whitespace and empty strings; trim the value client-side and
coerce empty/whitespace-only strings to null before sending (e.g., compute a
trimmed = values.gitRepositoryUrl?.trim() then set gitRepositoryUrl: trimmed &&
trimmed.length > 0 ? trimmed : null).

"Project slug must contain only lowercase letters, numbers, and hyphens",
),
gitRepositoryUrl: z.string().trim().url("Must be a valid URL").optional().or(z.literal("")),
gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")),
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)

Schema mismatch: gitRepositoryUrl accepts both nullable and empty string.

The schema allows gitRepositoryUrl to be null, "", or a valid URL. This creates ambiguity where both null and "" represent "no repository". Consider using only one sentinel value for consistency.

Apply this diff to use only nullable or only empty string:

-  gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")),
+  gitRepositoryUrl: z.string().trim().url("Must be a valid URL").optional().or(z.literal("")),

Or if you prefer nullable only:

-  gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")),
+  gitRepositoryUrl: z.union([z.string().trim().url("Must be a valid URL"), z.null()]),
📝 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
gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")),
gitRepositoryUrl: z.union([z.string().trim().url("Must be a valid URL"), z.null()]),
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/_components/create-project/create-project.schema.ts
around line 14 the gitRepositoryUrl schema currently allows both null and empty
string which is ambiguous; update the schema to use a single sentinel (prefer
nullable only): remove the .or(z.literal("")) branch so the field becomes
z.string().trim().url("Must be a valid URL").nullable(), ensuring the field
accepts a valid URL or null but not an empty string, and adjust any
callers/tests that relied on "" to use null instead.

Comment on lines 55 to +56
const fakeDeploymentId = "idk";
router.push(`/projects/${project.id}/deployments/${fakeDeploymentId}/logs`);
router.push(`/projects/${projectId}/deployments/${fakeDeploymentId}/logs`);
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)

⚠️ Potential issue

Using placeholder deployment ID for navigation.

The code uses a hardcoded "idk" as the deployment ID for navigation to logs and settings pages, which will likely cause issues.

The fake deployment ID "idk" needs to be replaced with actual deployment data. Would you like me to help implement a solution that fetches the active deployment ID for the project?

Consider either:

  1. Fetching the active deployment ID for the project
  2. Changing these routes to not require a deployment ID
  3. Disabling these menu items until a proper implementation is ready
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx around
lines 55-56, the code currently navigates using a hardcoded fake deployment ID
("idk"); replace this placeholder by retrieving the project's active deployment
ID and using it for routing (or disable the menu until available). Specifically:
fetch or accept the activeDeploymentId for the given projectId (via existing
data loader, a hook, or prop), guard the menu actions so they only enable when
activeDeploymentId is truthy, and use
router.push(`/projects/${projectId}/deployments/${activeDeploymentId}/logs`) and
similarly for settings; alternatively, if you cannot fetch it now, change the
menu items to be disabled or route to a deployment-less path so no hardcoded ID
is used.

Comment on lines 6 to +16
export const EnvironmentFilter = () => {
const { filters, updateFilters } = useFilters();

const environments = useLiveQuery((q) => q.from({ environment: collection.environments }));

return (
<FilterCheckbox
options={options}
options={environments.data.map((environment, i) => ({
id: i,
slug: environment.slug,
}))}
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)

Verify scope of environments.

Are environments workspace-wide or project-scoped? If workspace-wide, consider filtering by current project to avoid irrelevant options or slug collisions.

Also applies to: 18-25

🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx
around lines 6–16 (and similarly lines 18–25), verify whether environments are
workspace-wide or project-scoped; if environments are workspace-wide, change the
live query to filter environments by the current projectId (obtain projectId
from route params or parent props) so only relevant options are returned, and
ensure option identifiers avoid slug collisions by including a stable unique key
(e.g., environment ID or project-scoped composite) rather than relying solely on
index or slug.

Comment on lines +5 to +28
export const listDomains = t.procedure
.use(requireUser)
.use(requireWorkspace)
.use(withRatelimit(ratelimit.read))
.query(async ({ ctx }) => {
return await db.query.domains
.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
domain: true,
projectId: true,
type: true,
},
})
.catch((error) => {
console.error("Error querying domains:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Failed to retrieve domains due to an error. If this issue persists, please contact support.",
});
});
});
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)

Endpoint looks correct; add deterministic ordering

The auth/workspace scoping and read rate‑limit are good. Add orderBy for stable UI.

-    return await db.query.domains
-      .findMany({
+    return await db.query.domains
+      .findMany({
         where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
         columns: {
           id: true,
           domain: true,
           projectId: true,
           type: true,
         },
-      })
+        orderBy: (table, { asc }) => [asc(table.domain)],
+      })
📝 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
export const listDomains = t.procedure
.use(requireUser)
.use(requireWorkspace)
.use(withRatelimit(ratelimit.read))
.query(async ({ ctx }) => {
return await db.query.domains
.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
domain: true,
projectId: true,
type: true,
},
})
.catch((error) => {
console.error("Error querying domains:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Failed to retrieve domains due to an error. If this issue persists, please contact support.",
});
});
});
export const listDomains = t.procedure
.use(requireUser)
.use(requireWorkspace)
.use(withRatelimit(ratelimit.read))
.query(async ({ ctx }) => {
return await db.query.domains
.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
domain: true,
projectId: true,
type: true,
},
orderBy: (table, { asc }) => [asc(table.domain)],
})
.catch((error) => {
console.error("Error querying domains:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Failed to retrieve domains due to an error. If this issue persists, please contact support.",
});
});
});
🤖 Prompt for AI Agents
In apps/dashboard/lib/trpc/routers/domains/list.ts around lines 5 to 28, the
query returns domains without a deterministic order which can cause unstable UI
lists; modify the db.query.domains.findMany call to include an orderBy clause
(for example order by table.id ascending or table.domain ascending) so results
are consistently sorted; add the orderBy entry alongside the where and columns
options and keep ordering deterministic (e.g., { orderBy: (table, { asc }) =>
asc(table.id) }) to ensure stable UI rendering.

Comment on lines +10 to +17
return await db.query.environments.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
projectId: true,
slug: 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)

Deterministic ordering by slug.

Stable ordering prevents UI jitter across renders.

       return await db.query.environments.findMany({
         where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
         columns: {
           id: true,
           projectId: true,
           slug: true,
         },
+        orderBy: (table, { asc }) => [asc(table.slug)],
       });
📝 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
return await db.query.environments.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
projectId: true,
slug: true,
},
});
return await db.query.environments.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
projectId: true,
slug: true,
},
orderBy: (table, { asc }) => [asc(table.slug)],
});
🤖 Prompt for AI Agents
In apps/dashboard/lib/trpc/routers/environment/list.ts around lines 10 to 17,
the environments.findMany query lacks a deterministic ordering which causes UI
jitter; modify the query to include an order by the slug (ascending) so results
are stable across renders while keeping the existing where/filter and returned
columns unchanged.

Comment on lines +5 to +30
export const listProjects = t.procedure
.use(requireUser)
.use(requireWorkspace)
.use(withRatelimit(ratelimit.read))
.query(async ({ ctx }) => {
return await db.query.projects
.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
name: true,
slug: true,
updatedAt: true,
gitRepositoryUrl: true,
activeDeploymentId: true,
},
})
.catch((error) => {
console.error("Error querying projects:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Failed to retrieve projects due to an error. If this issue persists, please contact support.",
});
});
});
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)

Scoped, column-selected query with rate limit: LGTM.

Auth, workspace scoping, selected columns, and error mapping look solid.

Consider adding orderBy updatedAt DESC for stable result ordering.

🤖 Prompt for AI Agents
In apps/dashboard/lib/trpc/routers/project/list.ts around lines 5 to 30, the
projects query returns rows without a deterministic order; add an orderBy clause
to sort by updatedAt descending to ensure stable, most-recent-first results.
Modify the db.query.projects.findMany call to include an orderBy (updatedAt
DESC) alongside the existing where and columns options so results are
consistently ordered by updatedAt in descending order.

`name` varchar(256) NOT NULL,
`slug` varchar(256) NOT NULL,
`git_repository_url` varchar(500),
`active_deployment_id` varchar(256),
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

Index projects.active_deployment_id for joins/lookups.

Active deployment will be read frequently; add a secondary index to avoid full table scans.

Add the index via Drizzle (preferred; this file is generated):

// internal/db/src/schema/projects.ts
 (table) => ({
   workspaceIdx: index("workspace_idx").on(table.workspaceId),
   workspaceSlugIdx: uniqueIndex("workspace_slug_idx").on(table.workspaceId, table.slug),
+  activeDeploymentIdx: index("active_deployment_idx").on(table.activeDeploymentId),
 }),

This will render to:

CREATE INDEX `active_deployment_idx` ON `projects` (`active_deployment_id`);

Given PlanetScale/Vitess (hinted by planetscale:3900) lacks FKs, enforce referential integrity app‑side when setting active_deployment_id (ensure the deployment exists and belongs to the same project/workspace).

🤖 Prompt for AI Agents
In go/pkg/db/schema.sql around line 316, the projects table defines
active_deployment_id but has no index; add a secondary index named
active_deployment_idx on projects(active_deployment_id) (preferably via the
Drizzle schema/migration so it’s generated into schema.sql), and update the
migrations accordingly; additionally, because PlanetScale/Vitess lacks FKs,
enforce referential integrity in application code when writing
active_deployment_id by checking the referenced deployment exists and belongs to
the same project/workspace before setting or updating the field.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

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.

Review continued from previous batch...

Comment on lines +76 to 81
domain="TODO"
commitTitle="Latest deployment"
commitDate={new Date(project.updatedAt || project.createdAt).toLocaleDateString()}
branch={project.branch || "main"}
author="Unknown"
commitDate="TODO"
branch="TODO"
author="TODO"
regions={["us-east-1", "us-west-2", "ap-east-1"]}
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

Replace TODO placeholders with actual deployment data.

The card is rendering with hard-coded TODO values for domain, commit date, branch, and author. These should be populated from actual deployment data or removed if not available.

Consider either:

  1. Fetching deployment details for each project and displaying actual values
  2. Removing these fields from the card if the data is not available
  3. Showing a more appropriate placeholder or loading state
-                domain="TODO"
                 commitTitle="Latest deployment"
-                commitDate="TODO"
-                branch="TODO"
-                author="TODO"
+                domain={project.primaryDomain || "No domain configured"}
+                commitTitle={project.latestDeployment?.gitCommitMessage || "No deployments yet"}
+                commitDate={project.latestDeployment?.createdAt ? new Date(project.latestDeployment.createdAt).toLocaleDateString() : "N/A"}
+                branch={project.latestDeployment?.gitBranch || "N/A"}
+                author={project.latestDeployment?.gitCommitAuthorUsername || "N/A"}
📝 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
domain="TODO"
commitTitle="Latest deployment"
commitDate={new Date(project.updatedAt || project.createdAt).toLocaleDateString()}
branch={project.branch || "main"}
author="Unknown"
commitDate="TODO"
branch="TODO"
author="TODO"
regions={["us-east-1", "us-west-2", "ap-east-1"]}
domain={project.primaryDomain || "No domain configured"}
commitTitle={project.latestDeployment?.gitCommitMessage || "No deployments yet"}
commitDate={project.latestDeployment?.createdAt ? new Date(project.latestDeployment.createdAt).toLocaleDateString() : "N/A"}
branch={project.latestDeployment?.gitBranch || "N/A"}
author={project.latestDeployment?.gitCommitAuthorUsername || "N/A"}
regions={["us-east-1", "us-west-2", "ap-east-1"]}

commitDate="TODO"
branch="TODO"
author="TODO"
regions={["us-east-1", "us-west-2", "ap-east-1"]}
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Hard-coded regions array should be derived from actual data.

The regions array is hard-coded with specific values that may not reflect the actual deployment regions.


🏁 Script executed:

#!/bin/bash
# Check if there's a regions field in the project or deployment data structures
rg -nP --type=ts 'regions.*:.*Array' apps/dashboard/lib/collections/

Length of output: 0


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== show target file ==="
file="apps/dashboard/app/(app)/projects/_components/list/index.tsx"
if [ -f "$file" ]; then
  sed -n '1,240p' "$file"
else
  echo "NOT FOUND: $file"
fi

echo
echo "=== rg occurrences of 'regions' ==="
rg -n --hidden -S '\bregions\b' || true

echo
echo "=== rg occurrences of common region literals ==="
rg -n --hidden -S 'us-east-1|us-west-2|ap-east-1|eu-west-1|ap-northeast-1' || true

Length of output: 20876


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== ls collections dir ==="
ls -la apps/dashboard/lib/collections || true

echo
echo "=== show collections index ==="
sed -n '1,240p' apps/dashboard/lib/collections/index.ts || true

echo
echo "=== show projects collection (if present) ==="
sed -n '1,240p' apps/dashboard/lib/collections/projects.ts || true

echo
echo "=== show deployments collection ==="
sed -n '1,240p' apps/dashboard/lib/collections/deployments.ts || true

echo
echo "=== show projects-card.tsx ==="
sed -n '1,240p' "apps/dashboard/app/(app)/projects/_components/list/projects-card.tsx" || true

echo
echo "=== search for runtimeConfig and project fields ==="
rg -n --hidden -S 'runtimeConfig|gitRepositoryUrl|regions' apps/dashboard || true

Length of output: 15527


Derive regions from deployment runtime data (don’t hard-code)
Replace the hard-coded regions array in apps/dashboard/app/(app)/projects/_components/list/index.tsx:81 with the project’s actual regions (deployment.runtimeConfig.regions). runtimeConfig.regions is available in apps/dashboard/lib/collections/deployments.ts — either include it in trpc.project.list or resolve via the deployments collection / project.activeDeploymentId.

🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/_components/list/index.tsx around line 81,
the regions prop is hard-coded to ["us-east-1", "us-west-2", "ap-east-1"];
replace this with the project’s actual regions sourced from
deployment.runtimeConfig.regions. Update the data-fetch path to return
runtimeConfig.regions (either add it to trpc.project.list response or fetch the
active deployment via the deployments collection using
project.activeDeploymentId and read runtimeConfig.regions) and pass that array
into the regions prop, falling back to an empty array if undefined.

Comment on lines +180 to +184
<span className="text-gray-12 font-medium">{3}</span> to{" "}
<span className="text-gray-12 font-medium">{6}</span> instances
</div>
<div className="mt-0.5">
at <span className="text-gray-12 font-medium">{details.scaling.threshold}%</span> CPU
threshold
at <span className="text-gray-12 font-medium">70%</span> CPU threshold
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

Hard-coded scaling values should use actual configuration.

The scaling configuration shows hard-coded values (3 to 6 instances at 70% CPU) instead of using actual deployment configuration.

If scaling configuration is available in the deployment data, use it. Otherwise, consider removing this section or clearly indicating these are default values:

           <div className="text-grayA-10">
             <div>
-              <span className="text-gray-12 font-medium">{3}</span> to{" "}
-              <span className="text-gray-12 font-medium">{6}</span> instances
+              <span className="text-gray-12 font-medium">{details.scaling?.min || 3}</span> to{" "}
+              <span className="text-gray-12 font-medium">{details.scaling?.max || 6}</span> instances
             </div>
             <div className="mt-0.5">
-              at <span className="text-gray-12 font-medium">70%</span> CPU threshold
+              at <span className="text-gray-12 font-medium">{details.scaling?.cpuThreshold || 70}%</span> CPU threshold
             </div>
           </div>
📝 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
<span className="text-gray-12 font-medium">{3}</span> to{" "}
<span className="text-gray-12 font-medium">{6}</span> instances
</div>
<div className="mt-0.5">
at <span className="text-gray-12 font-medium">{details.scaling.threshold}%</span> CPU
threshold
at <span className="text-gray-12 font-medium">70%</span> CPU threshold
<div className="text-grayA-10">
<div>
<span className="text-gray-12 font-medium">{details.scaling?.min || 3}</span> to{" "}
<span className="text-gray-12 font-medium">{details.scaling?.max || 6}</span> instances
</div>
<div className="mt-0.5">
at <span className="text-gray-12 font-medium">{details.scaling?.cpuThreshold || 70}%</span> CPU threshold
</div>
</div>

Comment on lines +34 to +43
const deployments = useLiveQuery((q) =>
q
.from({ deployment: collection.deployments })
.where(({ deployment }) => eq(deployment.projectId, params.projectId))
.join({ environment: collection.environments }, ({ environment, deployment }) =>
eq(environment.id, deployment.environmentId),
)
.orderBy(({ deployment }) => deployment.createdAt, "desc")
.limit(100),
);
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 extracting the shared join logic into a reusable hook.

The live query pattern with deployment-environment join is duplicated across multiple components. Extract this into a shared hook to maintain consistency and reduce duplication.

Consider creating a reusable hook:

+// In a new file: apps/dashboard/hooks/use-deployments-with-environments.ts
+export const useDeploymentsWithEnvironments = (projectId: string, limit = 100) => {
+  return useLiveQuery((q) =>
+    q
+      .from({ deployment: collection.deployments })
+      .where(({ deployment }) => eq(deployment.projectId, projectId))
+      .join({ environment: collection.environments }, ({ environment, deployment }) =>
+        eq(environment.id, deployment.environmentId),
+      )
+      .orderBy(({ deployment }) => deployment.createdAt, "desc")
+      .limit(limit),
+  );
+};

Then use it in this component:

-  const deployments = useLiveQuery((q) =>
-    q
-      .from({ deployment: collection.deployments })
-      .where(({ deployment }) => eq(deployment.projectId, params.projectId))
-      .join({ environment: collection.environments }, ({ environment, deployment }) =>
-        eq(environment.id, deployment.environmentId),
-      )
-      .orderBy(({ deployment }) => deployment.createdAt, "desc")
-      .limit(100),
-  );
+  const deployments = useDeploymentsWithEnvironments(params.projectId);
📝 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 deployments = useLiveQuery((q) =>
q
.from({ deployment: collection.deployments })
.where(({ deployment }) => eq(deployment.projectId, params.projectId))
.join({ environment: collection.environments }, ({ environment, deployment }) =>
eq(environment.id, deployment.environmentId),
)
.orderBy(({ deployment }) => deployment.createdAt, "desc")
.limit(100),
);
const deployments = useDeploymentsWithEnvironments(params.projectId);
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
around lines 34 to 43, the live query that joins deployments to environments is
duplicated elsewhere; extract that logic into a reusable hook (e.g.,
useProjectDeployments) that accepts projectId (and optional limit/order) and
returns the live query result. Move the q.from/.where/.join/.orderBy/.limit
chain into the hook, inject collection and useLiveQuery inside the hook, and
replace the inline query in this file with a call to the new hook to keep
behavior identical.

Comment on lines +60 to +62
const sortedDeployments = deployments.data.sort(
(a, b) => b.deployment.createdAt - a.deployment.createdAt,
);
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)

Verify that the sort operation after data retrieval doesn't cause performance issues.

The deployments are already ordered by createdAt descending in the query (line 41), but they're being sorted again here. This redundant sort could impact performance with large datasets.

Either remove the redundant sort or consolidate the ordering logic:

-  const sortedDeployments = deployments.data.sort(
-    (a, b) => b.deployment.createdAt - a.deployment.createdAt,
-  );
+  // Data is already sorted by createdAt desc in the query
+  const sortedDeployments = deployments.data;
📝 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 sortedDeployments = deployments.data.sort(
(a, b) => b.deployment.createdAt - a.deployment.createdAt,
);
// Data is already sorted by createdAt desc in the query
const sortedDeployments = deployments.data;
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
around lines 60 to 62, the client-side sort redundantly re-sorts deployments
that are already ordered by createdAt in the query; remove the .sort(...) call
and use the deployments.data array directly, or if you want a safeguard, only
sort when the query does not guarantee ordering (e.g., check a flag or
responses' metadata) or move/ensure the ordering to the server/query (add an
orderBy createdAt desc) so no extra client-side sort runs on large datasets.

Comment on lines +142 to +148
sortedDeployments.map(({ deployment, environment }) => {
const commitSha =
deployment.gitCommitSha?.substring(0, 7) ||
deployment.id.substring(0, 7);
const branch = deployment.gitBranch || "unknown";
const date = new Date(deployment.createdAt).toLocaleDateString();

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)

Typo in template literal - extra '$' character.

The secondary text in the SelectItem contains a '$' character that appears to be a typo.

Fix the typo in both the "From" and "To" sections:

-  {environment?.slug} • ${date}
+  {environment?.slug} • {date}

Apply the same fix on line 250.

Also applies to: 172-173

🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
around lines 142-148 (and also fix the same typo at lines 172-173 and 250),
there is an extra '$' character in the template literal used for the SelectItem
secondary text; remove the stray '$' so the string interpolation is correct
(e.g., change `${$...}` or similar to `${...}` or plain text as intended) in
both the "From" and "To" SelectItem sections.

Comment on lines +103 to +104
deployment.gitCommitSha?.substring(0, 7) || deployment.id.substring(0, 7);
const branch = deployment.gitBranch || "unknown";
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)

Potential null reference when gitCommitSha is null.

When deployment.gitCommitSha is null, the code falls back to using deployment.id.substring(0, 7), but deployment IDs might not always be suitable for display as commit SHAs.

Consider using a more descriptive fallback:

-                        const commitSha =
-                          deployment.gitCommitSha?.substring(0, 7) || deployment.id.substring(0, 7);
+                        const commitSha = deployment.gitCommitSha?.substring(0, 7) || "no-commit";
📝 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
deployment.gitCommitSha?.substring(0, 7) || deployment.id.substring(0, 7);
const branch = deployment.gitBranch || "unknown";
const commitSha = deployment.gitCommitSha?.substring(0, 7) || "no-commit";
const branch = deployment.gitBranch || "unknown";
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx around lines 103
to 104, the current expression may call substring on deployment.id when
gitCommitSha is null which can still be unsuitable or cause issues; update the
logic to pick a descriptive, safe fallback: first use deployment.gitCommitSha if
present, otherwise use a validated deployment.id only if it looks like an ID
(and has sufficient length) or else fall back to a literal like "unknown" (apply
optional chaining and length checks before slicing, and use slice(0,7) instead
of substring to be explicit).

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: 45

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (23)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (5)

95-107: Fix shimmer z-index; current overlay can occlude icon/label.

Tailwind doesn’t ship a z-5 utility by default. If z-5 isn’t configured, the shimmer layer will sit above content and reduce readability/interactions.

Apply this diff to ensure proper stacking and pointer behavior:

@@
-      {animated && (
-        <div
-          className="absolute inset-0 bg-gradient-to-r from-transparent via-white/40 to-transparent w-[150%]"
-          style={{
-            animation: "shimmer 1.2s ease-in-out infinite",
-          }}
-        />
-      )}
+      {animated && (
+        <div
+          className="absolute inset-0 z-0 pointer-events-none bg-gradient-to-r from-transparent via-white/40 to-transparent w-[150%]"
+          style={{
+            animation: "shimmer 1.2s ease-in-out infinite",
+          }}
+        />
+      )}
@@
-      <Icon
-        size={config.icon === Nut ? "md-bold" : "md-regular"}
-        className={cn(iconColor, animated && "relative z-5")}
-      />
-      <span className={cn(textColor, "text-xs", animated && "relative z-5")}>{label}</span>
+      <Icon
+        size={config.icon === Nut ? "md-bold" : "md-regular"}
+        className={cn(iconColor, animated && "relative z-10")}
+      />
+      <span className={cn(textColor, "text-xs", animated && "relative z-10")}>{label}</span>

Bonus: consider adding a motion preference guard to disable shimmer for users with reduced motion.

Also applies to: 109-121


15-22: Broaden icon component type for flexibility.

Some icons may be memo/forwardRef components; FC can be too narrow. Use React.ComponentType.

Apply:

-type StatusConfig = {
-  icon: FC<IconProps>;
+type StatusConfig = {
+  icon: React.ComponentType<IconProps>;

24-31: Minor token inconsistency for gray.

text-gray-11 vs text-grayA-11. If the design system prefers A‑palette for text on translucent backgrounds, align pending’s icon color to text-grayA-11.

-    iconColor: "text-gray-11",
+    iconColor: "text-grayA-11",

80-82: Don’t throw on unknown status in UI; degrade gracefully.

DB‑driven enums can drift. Throwing here can crash the page; prefer a safe fallback (“Unknown” badge) and log.

-  if (!config) {
-    throw new Error(`Invalid deployment status: ${status}`);
-  }
+  if (!config) {
+    console.warn("Unknown deployment status:", status);
+    return (
+      <div className={cn("items-center flex gap-2 p-1.5 rounded-md w-fit", "bg-grayA-3", className)}>
+        <CircleWarning className="text-grayA-11" size="md-regular" />
+        <span className={cn("text-grayA-11", "text-xs")}>Unknown</span>
+      </div>
+    );
+  }

10-10: Import IconProps from the package root, not a deep src path

internal/icons/src/index.ts re-exports ./props — change the deep import to the public entry:

-import type { IconProps } from "@unkey/icons/src/props";
+import type { IconProps } from "@unkey/icons";

File: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx:10

apps/dashboard/lib/trpc/routers/project/create.ts (2)

102-110: Environment inserts miss required lifecycle fields.

environments.created_at is NOT NULL in the SQL schema; omitting timestamps risks a transaction failure when creating the project.

Add timestamps (mirroring projects insert):

           await tx.insert(schema.environments).values({
             id: environmentId,
             workspaceId,
             projectId,
             slug: slug,
+            createdAt: now,
+            updatedAt: now,
           });

146-152: Include updatedAt in the create response.
apps/dashboard/lib/collections/projects.ts declares updatedAt: z.number().int().nullable() (and shows updatedAt: null); apps/dashboard/lib/trpc/routers/project/create.ts (lines 146–152) returns only createdAt — return updatedAt: now to match consumers and avoid breaking optimistic updates.

       return {
         id: projectId,
         name: input.name,
         slug: input.slug,
         gitRepositoryUrl: input.gitRepositoryUrl,
-        createdAt: now,
+        createdAt: now,
+        updatedAt: now,
       };
go/pkg/db/schema.sql (1)

297-308: Environment uniqueness is too coarse; creation of multiple projects per workspace will fail.

UNIQUE(workspace_id, slug) prevents two projects in the same workspace from each having the same environment slug (e.g. the default "production") — make the uniqueness project-scoped.

Use this SQL change:

-CREATE TABLE `environments` (
+CREATE TABLE `environments` (
@@
-  CONSTRAINT `environments_workspace_id_slug_idx` UNIQUE(`workspace_id`,`slug`)
+  CONSTRAINT `environments_project_id_slug_idx` UNIQUE(`project_id`,`slug`)
 );

(Optional) add:

CREATE INDEX `project_slug_idx` ON `environments` (`project_id`,`slug`);

Update the generated schema file:

  • internal/db/src/schema/environments.ts — replace
    uniqueIndex("environments_workspace_id_slug_idx").on(table.workspaceId, table.slug)
    with
    uniqueIndex("environments_project_id_slug_idx").on(table.projectId, table.slug)
    and re-generate the TS schema.
apps/dashboard/lib/trpc/routers/deployment/list.ts (1)

5-8: Add read rate limiting for parity with other routes.

Matches project.list protections.

Apply:

-export const listDeployments = t.procedure
-  .use(requireUser)
-  .use(requireWorkspace)
+export const listDeployments = t.procedure
+  .use(requireUser)
+  .use(requireWorkspace)
+  .use(withRatelimit(ratelimit.read))

And update imports:

-import { requireUser, requireWorkspace, t } from "../../trpc";
+import { ratelimit, requireUser, requireWorkspace, t, withRatelimit } from "../../trpc";
apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (1)

42-45: Add clipboard API availability check

The clipboard write operation could fail if the Clipboard API is unavailable (e.g., in non-HTTPS contexts or older browsers).

Add a fallback for environments where the Clipboard API is unavailable:

       onClick: () => {
+        if (!navigator.clipboard) {
+          toast.error("Clipboard not available in this context");
+          return;
+        }
         navigator.clipboard
           .writeText(projectId)
           .then(() => {
             toast.success("Project ID copied to clipboard");
           })
           .catch((error) => {
             console.error("Failed to copy to clipboard:", error);
             toast.error("Failed to copy to clipboard");
           });
       },
apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (3)

89-102: Runtime error will crash the filter expansion

The expandGroupedStatus function throws an error for unknown statuses, which could crash the application at runtime if an unexpected value is passed. This is particularly risky during data migrations or when dealing with external data.

Consider a safer approach that logs a warning and returns a sensible default:

 export const expandGroupedStatus = (groupedStatus: GroupedDeploymentStatus): DeploymentStatus[] => {
   switch (groupedStatus) {
     case "pending":
       return ["pending"];
     case "deploying":
       return ["building", "deploying", "network"];
     case "ready":
       return ["ready"];
     case "failed":
       return ["failed"];
     default:
-      throw new Error(`Unknown grouped status: ${groupedStatus}`);
+      console.error(`Unknown grouped status: ${groupedStatus}, defaulting to pending`);
+      return ["pending"];
   }
 };

52-54: Orphaned "completed" status reference in color mapping

The getColorClass function still references "completed" status at line 53, but this status has been removed from both DEPLOYMENT_STATUSES and GROUPED_DEPLOYMENT_STATUSES. This dead code should be removed.

Remove the orphaned status check:

     getColorClass: (value) => {
-      if (value === "completed") {
-        return "bg-success-9";
-      }
       if (value === "failed") {
         return "bg-error-9";
       }
       if (value === "pending") {
         return "bg-gray-9";
       }
-      return "bg-info-9"; // building
+      return "bg-info-9"; // deploying, ready, or other states
     },

108-110: Consider static validation instead of runtime error

The runtime check for empty filter field configuration could be replaced with TypeScript compile-time guarantees or moved to a test.

Consider using TypeScript's type system to ensure non-empty configuration:

-if (allFilterFieldNames.length === 0) {
-  throw new Error("deploymentListFilterFieldConfig must contain at least one field definition.");
-}
+// TypeScript already ensures deploymentListFilterFieldConfig has required fields
+// via the FilterFieldConfigs type definition

Or move to a test file:

// In tests
describe('deploymentListFilterFieldConfig', () => {
  it('should have at least one field defined', () => {
    expect(Object.keys(deploymentListFilterFieldConfig).length).toBeGreaterThan(0);
  });
});
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx (1)

139-141: Async operation in event handler without error handling

The edit button's onClick handler calls handleToggleSecret() asynchronously but doesn't handle potential errors. If decryption fails, the edit mode will still be triggered with incomplete data.

Add error handling to prevent entering edit mode with failed decryption:

         onClick={() => {
-          handleToggleSecret().then(() => setIsEditing(true));
+          handleToggleSecret()
+            .then(() => setIsEditing(true))
+            .catch((error) => {
+              console.error("Failed to decrypt before editing:", error);
+              toast.error("Failed to decrypt secret");
+            });
         }}
apps/dashboard/app/(app)/projects/_components/create-project/create-project-dialog.tsx (1)

44-52: Generic error handling loses specific error context

The catch block only logs to console without providing user feedback for specific error cases. Users won't know why their project creation failed.

Improve error handling to provide meaningful user feedback:

   const onSubmitForm = async (values: FormValues) => {
     try {
       await createProject.mutateAsync({
         name: values.name,
         slug: values.slug,
         gitRepositoryUrl: values.gitRepositoryUrl ?? null,
       });
     } catch (error) {
       console.error("Form submission error:", error);
+      
+      // Extract error message if available
+      const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
+      
+      // Check for specific error types
+      if (errorMessage.includes("duplicate") || errorMessage.includes("already exists")) {
+        toast.error("Project already exists", {
+          description: "A project with this name or slug already exists"
+        });
+      } else if (errorMessage.includes("unauthorized") || errorMessage.includes("permission")) {
+        toast.error("Permission denied", {
+          description: "You don't have permission to create projects"
+        });
+      } else {
+        toast.error("Failed to create project", {
+          description: errorMessage
+        });
+      }
     }
   };
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx (1)

59-79: Incomplete save implementation needs attention.

The save handler has multiple TODOs and no actual persistence:

  • No real mutation implementation
  • Mock delay instead of actual save
  • Commented-out invalidation logic
  • Error thrown but no user feedback mechanism

This incomplete implementation could confuse users who expect their changes to persist.

Would you like me to help implement the actual save logic or create an issue to track this incomplete functionality?

apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx (1)

8-12: Unused projectId prop in type definition.

The projectId prop is defined in AddEnvVarRowProps but not used in the component's parameters.

 type AddEnvVarRowProps = {
-  projectId: string;
   getExistingEnvVar: (key: string, excludeId?: string) => EnvVar | undefined;
   onCancel: () => void;
 };
apps/dashboard/lib/collections/index.ts (1)

27-34: Add error handling for the reset function.

The reset() function doesn't handle potential errors during cleanup or preload operations, which could leave collections in an inconsistent state.

Apply this diff to add error handling:

 export async function reset() {
-  await Promise.all(
-    Object.values(collection).map(async (c) => {
-      await c.cleanup();
-      await c.preload();
-    }),
-  );
+  const errors: Array<{ collection: string; error: unknown }> = [];
+  
+  await Promise.all(
+    Object.entries(collection).map(async ([name, c]) => {
+      try {
+        await c.cleanup();
+        await c.preload();
+      } catch (error) {
+        errors.push({ collection: name, error });
+        console.error(`Failed to reset collection ${name}:`, error);
+      }
+    }),
+  );
+  
+  if (errors.length > 0) {
+    throw new Error(`Failed to reset ${errors.length} collection(s)`);
+  }
 }
apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (1)

101-141: Consider extracting duplicate deployment selector logic.

The deployment selection UI code is duplicated between the "From" and "To" sections (lines 101-178 and 193-257). This violates the DRY principle and makes maintenance harder.

Consider extracting a reusable component:

interface DeploymentSelectorProps {
  label: string;
  value: string;
  onChange: (value: string) => void;
  deployments: typeof deployments;
  sortedDeployments: typeof sortedDeployments;
  placeholder: string;
}

function DeploymentSelector({ label, value, onChange, deployments, sortedDeployments, placeholder }: DeploymentSelectorProps) {
  return (
    <div>
      <label className="block text-sm font-medium text-content mb-2">
        {label}
      </label>
      <Select value={value} onValueChange={onChange} disabled={deployments.isLoading || deployments.data.length === 0}>
        <SelectTrigger className="h-auto">
          <SelectValue placeholder={placeholder} />
        </SelectTrigger>
        <SelectContent>
          {/* ... existing content ... */}
        </SelectContent>
      </Select>
    </div>
  );
}
apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (1)

191-257: Consider uncommenting or removing the Build Info section.

The entire Build Info section is commented out. If this feature is not ready, consider removing the code entirely rather than keeping it commented. If it's temporarily disabled, add a TODO comment explaining why and when it should be re-enabled.

apps/dashboard/lib/shorten-id.ts (1)

8-9: Breaking change: shortenId default startChars changed 8 → 4 — revert or update callers

shortenId's default was reduced from 8 to 4; rg found 6 call sites, 5 of which call shortenId(...) with no options and will be affected:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
  • apps/dashboard/app/(app)/apis/[apiId]/api-id-navbar.tsx
  • apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/_components/components/table/keys-list.tsx
  • apps/dashboard/app/(app)/apis/[apiId]/_overview/components/table/components/override-indicator.tsx
  • apps/dashboard/app/(app)/audit/components/controls/components/logs-filters/components/root-keys-filter.tsx
    (one call passes explicit options: apps/.../keys/[keyAuthId]/[keyId]/components/table/logs-table.tsx)

Actions:

  • Revert shortenId default startChars to 8, OR
  • Update affected call sites to pass { startChars: 8 } where longer prefixes are required, and add/update tests/changelog to reflect the change.
apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts (1)

18-49: Handle UNAUTHORIZED explicitly.

tRPC commonly returns UNAUTHORIZED distinct from FORBIDDEN. Add a branch for clearer UX.

Apply this diff inside onError:

       } else if (err.data?.code === "CONFLICT") {
         toast.error("Project Already Exists", {
           description: err.message || "A project with this slug already exists in your workspace.",
         });
+      } else if (err.data?.code === "UNAUTHORIZED") {
+        toast.error("Sign-in required", {
+          description: "Please sign in again to create a project.",
+        });
       } else if (err.data?.code === "INTERNAL_SERVER_ERROR") {
apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (1)

194-206: Use a stable unique key.

Using label as the React key risks collisions. Prefer an id or href.

Apply this diff:

-            {projectAddedNavItems.map((item) => (
-              <div
-                key={item.label as string}
+            {projectAddedNavItems.map((item) => (
+              <div
+                key={(item as any).id ?? item.href}
♻️ Duplicate comments (4)
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx (1)

46-64: Mock save implementation lacks user feedback.

Similar to the env-var-form, this component has:

  • No actual persistence
  • Mock 500ms delay
  • Commented TODO sections
  • Console error but no UI feedback
apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (2)

60-62: Array mutation issue - same as in diff/page.tsx.

This file has the same array mutation issue with sortedDeployments directly sorting deployments.data.

Apply this diff to avoid mutating the original array:

-  const sortedDeployments = deployments.data.sort(
-    (a, b) => b.deployment.createdAt - a.deployment.createdAt,
-  );
+  const sortedDeployments = [...deployments.data].sort(
+    (a, b) => b.deployment.createdAt - a.deployment.createdAt,
+  );

250-250: Fix typo in template literal (duplicate).

Same typo appears in the "To" selection section.

-                                      {environment?.slug} • ${date}
+                                      {environment?.slug} • {date}
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1)

5-12: Update styling logic to match new default title.

You changed the default title to “Select Time Range” but still compare against "Last 12 hours". This now forces bg-gray-4 even when no range is selected.

Apply this diff:

-const TITLE_EMPTY_DEFAULT = "Select Time Range";
+const TITLE_EMPTY_DEFAULT = "Select Time Range";
@@
-            title ? "" : "opacity-50",
-            title !== "Last 12 hours" ? "bg-gray-4" : "",
+            title ? "" : "opacity-50",
+            title !== TITLE_EMPTY_DEFAULT ? "bg-gray-4" : "",

Also consider applying the same “reset title when cleared” pattern to all time filters, per prior discussion.

Also applies to: 14-25, 82-86

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 10f390e and 324c314.

📒 Files selected for processing (55)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/deployment-status-filter.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx (11 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/hooks/use-deployments-list-query.ts (0 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx (6 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/skeleton.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx (12 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (7 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (8 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx (0 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx (3 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx (4 hunks)
  • apps/dashboard/app/(app)/projects/[projectId]/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/create-project-dialog.tsx (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/create-project.schema.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts (1 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/hooks/use-projects-list-query.ts (0 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/index.tsx (3 hunks)
  • apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx (4 hunks)
  • apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (2 hunks)
  • apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (1 hunks)
  • apps/dashboard/lib/collections/deployments.ts (1 hunks)
  • apps/dashboard/lib/collections/domains.ts (1 hunks)
  • apps/dashboard/lib/collections/environments.ts (1 hunks)
  • apps/dashboard/lib/collections/index.ts (1 hunks)
  • apps/dashboard/lib/collections/projects.ts (1 hunks)
  • apps/dashboard/lib/collections/ratelimit_namespaces.ts (1 hunks)
  • apps/dashboard/lib/collections/ratelimit_overrides.ts (1 hunks)
  • apps/dashboard/lib/shorten-id.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/active-deployment/getDetails.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/deployment/list.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deploy/project/list.ts (0 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/domains/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/environment/list.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/index.ts (3 hunks)
  • apps/dashboard/lib/trpc/routers/project/create.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/project/list.ts (1 hunks)
  • go/pkg/db/schema.sql (1 hunks)
  • internal/db/src/schema/projects.ts (1 hunks)
💤 Files with no reviewable changes (6)
  • apps/dashboard/app/(app)/projects/_components/list/hooks/use-projects-list-query.ts
  • apps/dashboard/app/(app)/projects/[projectId]/layout-provider.tsx
  • apps/dashboard/lib/trpc/routers/deploy/project/list.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/deployment/list.ts
  • apps/dashboard/lib/trpc/routers/deploy/project/active-deployment/getDetails.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/hooks/use-deployments-list-query.ts
🧰 Additional context used
🧠 Learnings (14)
📓 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.
📚 Learning: 2025-07-25T19:09:43.284Z
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.

Applied to files:

  • apps/dashboard/lib/trpc/routers/environment/list.ts
  • apps/dashboard/lib/trpc/routers/domains/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx
  • apps/dashboard/lib/trpc/routers/project/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.ts
  • apps/dashboard/lib/collections/deployments.ts
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx
  • apps/dashboard/lib/trpc/routers/deployment/list.ts
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/layout.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/filters.schema.ts
📚 Learning: 2025-08-31T19:30:38.171Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3853
File: apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts:75-87
Timestamp: 2025-08-31T19:30:38.171Z
Learning: In apps/dashboard/lib/trpc/routers/deploy/project/envs/list.ts, the getEnvs procedure currently uses mock data (VARIABLES) and ignores the projectId input parameter. This is intentional temporary behavior - the user ogzhanolguncu indicated they plan to hook this up to the database later.

Applied to files:

  • apps/dashboard/lib/trpc/routers/environment/list.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx
  • apps/dashboard/lib/trpc/routers/project/list.ts
  • apps/dashboard/lib/collections/environments.ts
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx
📚 Learning: 2025-07-25T19:11:00.208Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/lib/trpc/routers/deployment/list.ts
  • apps/dashboard/lib/trpc/routers/index.ts
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
📚 Learning: 2025-01-22T16:51:59.978Z
Learnt from: MichaelUnkey
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx
📚 Learning: 2025-08-25T13:46:08.303Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3834
File: apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-datetime/index.tsx:1-1
Timestamp: 2025-08-25T13:46:08.303Z
Learning: The NamespaceListDateTime component in apps/dashboard/app/(app)/ratelimits/_components/controls/components/namespace-list-datetime/index.tsx is intentionally designed to use the overview hook (useFilters from @/app/(app)/ratelimits/[namespaceId]/_overview/hooks/use-filters) rather than the namespace list hook, as clarified by ogzhanolguncu. This coupling is by design, not an architectural issue.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx
📚 Learning: 2025-08-18T10:28:47.391Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3797
File: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/control-cloud/index.tsx:1-4
Timestamp: 2025-08-18T10:28:47.391Z
Learning: In Next.js App Router, components that use React hooks don't need their own "use client" directive if they are rendered within a client component that already has the directive. The client boundary propagates to child components.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
  • 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]/layout.tsx
📚 Learning: 2025-06-02T11:09:58.791Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx
📚 Learning: 2024-10-23T12:05:31.121Z
Learnt from: chronark
PR: unkeyed/unkey#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.

Applied to files:

  • apps/dashboard/lib/collections/ratelimit_namespaces.ts
  • apps/dashboard/lib/collections/ratelimit_overrides.ts
📚 Learning: 2025-08-25T12:56:59.310Z
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#3834
File: apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts:59-66
Timestamp: 2025-08-25T12:56:59.310Z
Learning: In the ratelimit namespace query system (apps/dashboard/lib/trpc/routers/ratelimit/query-namespaces/index.ts), the nameQuery filter is designed as an array for future extensibility to support multiple filters, but currently only the first filter (index 0) is processed. This is intentional future-proofing.

Applied to files:

  • apps/dashboard/lib/collections/ratelimit_namespaces.ts
📚 Learning: 2025-06-24T13:29:10.129Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx
📚 Learning: 2025-07-28T20:38:53.244Z
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.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
  • apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/page.tsx
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx
  • apps/dashboard/app/(app)/projects/_components/list/index.tsx
📚 Learning: 2025-08-22T12:50:07.024Z
Learnt from: perkinsjr
PR: unkeyed/unkey#3775
File: apps/dashboard/lib/trpc/routers/api/keys/query-overview-timeseries/index.ts:1-2
Timestamp: 2025-08-22T12:50:07.024Z
Learning: The team at Unkey is comfortable accepting TRPC schema imports from app route folders in server-side TRPC routers as technical debt, since they're planning to migrate away from TRPC to calling their v2 API directly, making these imports temporary and not worth refactoring.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx
📚 Learning: 2025-09-12T17:57:12.128Z
Learnt from: mcstepp
PR: unkeyed/unkey#3952
File: apps/dashboard/app/(app)/projects/[projectId]/deployments/components/rollback-dialog.tsx:111-116
Timestamp: 2025-09-12T17:57:12.128Z
Learning: In the Deployment type used across the dashboard deployment components, the source field is required and will always be present, so optional chaining (source?.branch) is not needed when accessing source.branch or source.gitSha.

Applied to files:

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
🔇 Additional comments (34)
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx (2)

40-47: Deploying: LGTM.

Config, animation, and tokens are consistent with “in‑progress” states.


56-62: Ready: LGTM.

Tokens and semantics look correct.

apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (1)

53-55: LGTM on router path switch.

The query shape and consumers remain compatible. Consider gating with enabled: !!deploymentId if deploymentId can ever be empty.

apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/deployment-status-filter.tsx (1)

12-33: Statuses updated correctly; ensure color map covers new states.

Given deploying/ready changes, verify deploymentListFilterFieldConfig.status.getColorClass maps these to non-default classes.

apps/dashboard/lib/collections/ratelimit_overrides.ts (1)

15-18: Type export + generic OK — verify create input doesn't require id.

You're parsing changes and sending it straight to trpcClient.ratelimit.override.create.mutate(schema.parse(changes)); confirm the server's create procedure does not generate the id server‑side. If the server generates ids, omit id before sending (e.g. schema.omit({ id: true }).parse(changes) or remove changes.id). Location: apps/dashboard/lib/collections/ratelimit_overrides.ts (onInsert → trpcClient.ratelimit.override.create.mutate(...)).

apps/dashboard/lib/collections/projects.ts (1)

13-15: Potential type drift: updatedAt as number.

TRPC often serializes Dates (native or via superjson). Ensure server returns a number (epoch) for updatedAt; otherwise switch this to z.date().nullable() or preprocess accordingly.

Would you like me to scan the repo for TRPC transformer usage (e.g., superjson) and the projects schema mapping to confirm the runtime type?

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

11-30: ```shell
#!/bin/bash
set -euo pipefail

echo "PWD: $(pwd)"
echo "=== check ripgrep ==="
if command -v rg >/dev/null 2>&1; then
echo "rg found"
else
echo "rg NOT found"
fi

echo "=== search for exact fields ==="
if command -v rg >/dev/null 2>&1; then
rg -n --hidden -S '\bgitCommitAuthorEmail\b' -C3 || true
rg -n --hidden -S '\bruntimeConfig\b' -C3 || true
rg -n --hidden -S '\bgitCommitAuthor[A-Za-z0-9_]\b' -C3 || true
rg -n --hidden -S '\bgitCommit[A-Za-z0-9_]
\b' -C3 || true
rg -n --hidden -S '\bdeployments\b' -C3 || true
rg -n --hidden -S 'findMany(' -C3 || true
rg -n --hidden -S 'columns:\s*{' -C3 || true
rg -n --hidden -S '\binterface\s+Deployment\b|\btype\s+Deployment\b|\bexport\s+type\s+Deployment\b' -C3 || true
else
grep -RIn --line-number --exclude-dir=node_modules --exclude-dir=.git -nE '\bgitCommitAuthorEmail\b' . || true
grep -RIn --line-number --exclude-dir=node_modules --exclude-dir=.git -nE '\bruntimeConfig\b' . || true
fi

echo "=== show target file if present ==="
if [ -f apps/dashboard/lib/trpc/routers/deployment/list.ts ]; then
sed -n '1,240p' apps/dashboard/lib/trpc/routers/deployment/list.ts
else
echo "File not found: apps/dashboard/lib/trpc/routers/deployment/list.ts"
fi

echo "=== search for frontend usage under apps/ (narrow) ==="
if command -v rg >/dev/null 2>&1; then
rg -n --hidden -S '\bgitCommitAuthorEmail\b' apps || true
rg -n --hidden -S '\bruntimeConfig\b' apps || true
rg -n --hidden -S '\bgitCommitAuthor[A-Za-z0-9_]*\b' apps || true
else
grep -RIn --line-number --exclude-dir=node_modules --exclude-dir=.git -nE '\bgitCommitAuthorEmail\b' apps || true
grep -RIn --line-number --exclude-dir=node_modules --exclude-dir=.git -nE '\bruntimeConfig\b' apps || true
fi


</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/_components/create-project/create-project-dialog.tsx (1)</summary><blockquote>

`47-47`: **No change required — backend accepts empty string for gitRepositoryUrl.**

create-project.schema.ts explicitly allows null OR empty string (z.string().trim().url(...).nullable().or(z.literal(''))), and the DB column git_repository_url is nullable (varchar(500)), so switching to `?? null` is safe.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsx (1)</summary><blockquote>

`3-3`: **Type-only import change looks good.**

The migration from TRPC router path to collections module aligns with the PR's architectural shift to TanStack React-DB.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsx (1)</summary><blockquote>

`23-32`: **Check empty result handling for activeProject query.**

The `.at(0)` call could return `undefined` if no project matches. While you handle this case later, consider whether this is expected behavior or indicates a data integrity issue.


Could this indicate that:
1. The project was deleted while the user is viewing it?
2. The user lacks permission to view this project?
3. There's a data synchronization issue?

Consider adding telemetry or logging when this occurs to understand the frequency and cause.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/layout.tsx (1)</summary><blockquote>

`41-41`: **Resolved — onMount signature is compatible with setTableDistanceToTop**

ProjectSubNavigation declares onMount: (distanceToTop: number) => void and calls onMount(distanceToTop); layout.tsx passes setTableDistanceToTop (useState updater) — compatible.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx (2)</summary><blockquote>

`1-1`: **LGTM! Import cleanup.**

The removal of `CircleWarning` import aligns with the simplified component that now only displays success state.

---

`4-6`: **Incorrect — removing `status`/`tags` is not a breaking change in this repo.**

Repo search shows DomainRowProps only defines `{ domain: string }` (apps/dashboard/app/(app)/projects/[projectId]/details/domain-row.tsx) and DomainRow is imported/used only at apps/dashboard/app/(app)/projects/[projectId]/page.tsx (line 61): `<DomainRow key={domain.id} domain={domain.domain} />`. No other usages/imports found. Verify external consumers only if this component is published/used outside this repository.  


> Likely an incorrect or invalid review comment.

</blockquote></details>
<details>
<summary>apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx (3)</summary><blockquote>

`3-4`: **LGTM! Clean migration to live collections.**

The import changes correctly replace TRPC with TanStack React-DB's collection-based approach.

---

`19-31`: **LGTM! Cleaner data mapping without pagination complexity.**

The simplified data structure improves code readability by removing the pagination layer.

---

`49-52`: **Resolved — removing `loadMore` is non-breaking (no callers use it).**

Repo search shows the only consumer is apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx:88 which destructures only `enhancedNavItems`; the hook at apps/dashboard/components/navigation/sidebar/app-sidebar/hooks/use-projects-navigation.tsx returns `{ enhancedNavItems, isLoading }`.

</blockquote></details>
<details>
<summary>apps/dashboard/lib/collections/index.ts (2)</summary><blockquote>

`3-8`: **LGTM! Well-organized collection imports.**

The new collection imports are properly structured and follow a consistent naming pattern.

---

`10-15`: **LGTM! Clean type exports.**

The exported types provide proper TypeScript support for consumers of these collections.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/_components/list/index.tsx (3)</summary><blockquote>

`1-2`: **LGTM! Clean migration to live queries.**

The imports correctly replace TRPC with TanStack React-DB's collection-based approach.

---

`16-32`: **LGTM! Improved loading state with skeleton UI.**

The skeleton loading state provides better user experience compared to the previous pagination approach.

---

`84-84`: **Verified — ProjectActions accepts projectId**

ProjectActionsProps declares `projectId: string` in apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx; passing `project.id` is correct.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsx (2)</summary><blockquote>

`1-2`: **LGTM! Clean migration to collections.**

The imports correctly establish the new data access pattern using TanStack React-DB.

---

`40-40`: **Resolved — createDetailSections already accepts Deployment.**
Defined as `export const createDetailSections = (details: Deployment): DetailSection[]` in apps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsx; no changes required.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/page.tsx (3)</summary><blockquote>

`44-52`: **LGTM! Clean conditional rendering.**

The conditional rendering of the Active Deployment section based on `project.activeDeploymentId` is well-implemented. The component now correctly passes the deployment ID to the `ActiveDeploymentCard` component.

---

`60-62`: **Verify DomainRow accepts a single string prop and all usages updated**

apps/dashboard/app/(app)/projects/[projectId]/page.tsx (lines 60–62) renders <DomainRow domain={domain.domain} /> — confirm DomainRow's declaration is ({ domain }: { domain: string }) and that no other call sites still expect the full domain object.

---

`16-18`: **Add explicit error handling for this useLiveQuery.**  
The useLiveQuery in apps/dashboard/app/(app)/projects/[projectId]/page.tsx (lines 16–18) currently ignores possible failures; verify whether @tanstack/react-db's hook exposes an error/isError value and either handle it (e.g., { data, error, isLoading }) or protect the component with an ErrorBoundary/fallback UI.

</blockquote></details>
<details>
<summary>apps/dashboard/lib/shorten-id.ts (1)</summary><blockquote>

`44-53`: **Prefix handling logic looks good.**

The implementation correctly preserves prefixes when present and applies shortening only to the portion after the underscore. The string building approach is clear and efficient.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/diff/page.tsx (1)</summary><blockquote>

`280-297`: **Effective data fetching pattern with TanStack React-DB.**

The migration from TRPC to TanStack React-DB's `useLiveQuery` is well-implemented. The join with environments collection and the ordering/limiting logic are appropriate for the use case.

</blockquote></details>
<details>
<summary>apps/dashboard/lib/trpc/routers/index.ts (1)</summary><blockquote>

`40-46`: **Router restructuring looks good**

The new flatter router structure improves organization by separating concerns into distinct top-level routes (`deployment`, `domain`, `environment`, `project`). This is cleaner than the previous nested `deploy.project.*` pattern.

</blockquote></details>
<details>
<summary>apps/dashboard/components/navigation/sidebar/app-sidebar/index.tsx (1)</summary><blockquote>

`84-89`: **Confirm UI parity after removing load‑more plumbing.**

With collections replacing infinite queries, verify that any “Load more …” buttons/UX are removed from NavItems and hooks to avoid dead props.


I can scan NavItems and hooks if you’d like a quick follow‑up script.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsx (1)</summary><blockquote>

`10-13`: **LGTM; ensure query is keyed by project.**

Switch to environmentVariables.list looks correct. Confirm the tRPC react client keys the query by input so cross‑project caching doesn’t leak.

</blockquote></details>
<details>
<summary>apps/dashboard/lib/collections/ratelimit_namespaces.ts (1)</summary><blockquote>

`13-16`: **LGTM; exported type improves reuse.**

The RatelimitNamespace export and generic update align the collection surface. No issues.

</blockquote></details>
<details>
<summary>apps/dashboard/lib/trpc/routers/deployment/llm-search/index.ts (1)</summary><blockquote>

`13-20`: **Incorrect — ctx.validatedQuery is injected by middleware; no runtime crash expected.**

trpc middleware sets ctx.validatedQuery (apps/dashboard/lib/trpc/trpc.ts:198) and another router uses it (apps/dashboard/lib/trpc/routers/logs/llm-search/index.ts:19). The current code is valid; change to use ({ input }) and pass input/input.query only if you want explicitness/consistency.  

 

> Likely an incorrect or invalid review comment.

</blockquote></details>
<details>
<summary>apps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-filters/components/environment-filter.tsx (1)</summary><blockquote>

`2-4`: **Use a stable key for option id — don't use array index.**

Index keys will break selection when the list changes; use environment.id (or slug if guaranteed unique).

```diff
-  const environments = useLiveQuery((q) => q.from({ environment: collection.environments }));
+  const environments = useLiveQuery((q) => q.from({ environment: collection.environments }));
@@
-      options={environments.data.map((environment, i) => ({
-        id: i,
-        slug: environment.slug,
+      options={environments.data.map((environment) => ({
+        id: environment.id,
+        slug: environment.slug,
       }))}

Confirm the environments collection is scoped to the active workspace/project; otherwise this filter may show environments from other contexts. Also applies to lines 9-16 and 18-25.

Comment on lines +14 to 15
gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")),
});
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

Normalize blank URLs to null; avoid union with empty string.

This makes whitespace-only values fail validation and perpetuates a third state ("") you later coerce to null on the server. Prefer coercing "" → null at the schema boundary.

Apply:

-  gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")),
+  gitRepositoryUrl: z.preprocess(
+    (v) => (typeof v === "string" && v.trim() === "" ? null : v),
+    z.string().trim().url("Must be a valid URL").nullable(),
+  ),
📝 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
gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")),
});
gitRepositoryUrl: z.preprocess(
(v) => (typeof v === "string" && v.trim() === "" ? null : v),
z.string().trim().url("Must be a valid URL").nullable(),
),
});
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/_components/create-project/create-project.schema.ts
around lines 14-15, the schema currently allows an empty string union for
gitRepositoryUrl which preserves a third state ("") and lets whitespace-only
values pass; update the zod schema to coerce trimmed empty or whitespace-only
strings to null at the boundary instead of using .or(z.literal("")) — apply
.transform(v => { const t = v?.trim(); return t === "" ? null : t }) (or use
z.preprocess to return null for blank input), then validate the non-null value
with .nullable().url(...) so blank inputs become null and only true URLs pass
validation.

Comment on lines +76 to +80
domain="TODO"
commitTitle="Latest deployment"
commitDate={new Date(project.updatedAt || project.createdAt).toLocaleDateString()}
branch={project.branch || "main"}
author="Unknown"
commitDate="TODO"
branch="TODO"
author="TODO"
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)

⚠️ Potential issue

Replace TODO placeholders with actual data.

The hardcoded "TODO" values for domain, commitDate, branch, and author should be replaced with actual data or removed if not yet available.

Would you like me to help implement the logic to fetch and display the actual deployment data for these fields? I can create a solution that retrieves this information from the appropriate collections.

Comment on lines 54 to +56
//INFO: This will change soon
const fakeDeploymentId = "idk";
router.push(`/projects/${project.id}/deployments/${fakeDeploymentId}/logs`);
router.push(`/projects/${projectId}/deployments/${fakeDeploymentId}/logs`);
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

Hardcoded fake deployment IDs will cause navigation failures

Using a hardcoded fakeDeploymentId = "idk" for navigation will likely result in 404 errors or incorrect routing when users click "View logs" or "Project settings". These routes expect valid deployment IDs.

The component should either:

  1. Fetch the actual active deployment ID for the project
  2. Navigate to project-level routes that don't require a deployment ID
  3. Disable these menu items until a valid deployment ID is available

Consider updating the routes or fetching the active deployment:

-        //INFO: This will change soon
-        const fakeDeploymentId = "idk";
-        router.push(`/projects/${projectId}/deployments/${fakeDeploymentId}/logs`);
+        // TODO: Fetch active deployment ID from the projects collection
+        // For now, navigate to deployments list instead
+        router.push(`/projects/${projectId}/deployments`);

Also applies to: 64-66

🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/_components/list/project-actions.tsx around
lines 54-56 (and similarly 64-66), a hardcoded fakeDeploymentId ("idk") is being
used for navigation which will cause 404s; replace this by retrieving the actual
active deployment ID for the given project (call the existing API/selector or
fetch project.deployments and pick the active/latest), and only call
router.push(`/projects/${projectId}/deployments/${deploymentId}/logs`) when a
valid deploymentId is present; alternatively, if you cannot fetch it here,
change the target to a project-level route that does not require a deploymentId
or disable/hide the "View logs" and "Project settings" menu items until a valid
deploymentId is available, and ensure you add a null/undefined guard and
user-facing fallback (disabled state or tooltip) when deploymentId is missing.

Comment on lines +5 to +17
export const listEnvironments = t.procedure
.use(requireUser)
.use(requireWorkspace)
.query(async ({ ctx }) => {
try {
return await db.query.environments.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
projectId: true,
slug: true,
},
});
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Scope results by project to reduce over-fetching and UI ambiguity.

Returning all workspace environments makes per‑project filters noisy and relies on a problematic workspace‑level uniqueness. Accept an optional projectId and filter server‑side.

-export const listEnvironments = t.procedure
+export const listEnvironments = t.procedure
+  .input(z.object({ projectId: z.string().optional() }).optional())
   .use(requireUser)
   .use(requireWorkspace)
-  .query(async ({ ctx }) => {
+  .query(async ({ ctx, input }) => {
     try {
       return await db.query.environments.findMany({
-        where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
+        where: (table, { eq, and }) =>
+          input?.projectId
+            ? and(eq(table.workspaceId, ctx.workspace.id), eq(table.projectId, input.projectId))
+            : eq(table.workspaceId, ctx.workspace.id),
         columns: {
           id: true,
           projectId: true,
           slug: true,
         },
       });

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== show file (if exists) ==="
if [ -f "apps/dashboard/lib/trpc/routers/environment/list.ts" ]; then
  sed -n '1,200p' apps/dashboard/lib/trpc/routers/environment/list.ts
else
  echo "FILE_NOT_FOUND: apps/dashboard/lib/trpc/routers/environment/list.ts"
fi

echo
echo "=== search for listEnvironments symbol ==="
rg -n "listEnvironments" || true

echo
echo "=== search for environments.findMany usage ==="
rg -n "environments\\.findMany" || true

echo
echo "=== search for .input( usages in trpc procedures ==="
rg -n --hidden --glob '!node_modules' "\\.input\\(" || true

echo
echo "=== search for projectId occurrences ==="
rg -n "projectId" || true

echo
echo "=== list router folder ==="
ls -la apps/dashboard/lib/trpc/routers || true

Length of output: 21679


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== show project envs file (if exists) ==="
if [ -f "apps/dashboard/lib/trpc/routers/project/envs/list.ts" ]; then
  sed -n '1,240p' apps/dashboard/lib/trpc/routers/project/envs/list.ts
else
  echo "FILE_NOT_FOUND: apps/dashboard/lib/trpc/routers/project/envs/list.ts"
fi

echo
echo "=== search for trpc environment list usage ==="
rg -n "environment\\.list" || true
rg -n "environments\\.list" || true
rg -n "trpc\\.environment" || true

echo
echo "=== search for trpc.*.environment usages ==="
rg -n "environmentVariables\\.list" || true
rg -n "trpc\\.environmentVariables" || true

Length of output: 2553


Scope environment.list by optional projectId (server-side filter).

Add an optional input (z.object({ projectId: z.string().optional() }).optional()) and apply server-side filtering so results are workspace-scoped OR workspace+project-scoped when projectId is provided; keep the input optional so existing callers continue to work. Add import { z } from "zod" and update the query handler to accept input and filter with both workspaceId and projectId when present. Caller to review: apps/dashboard/lib/collections/environments.ts (uses trpcClient.environment.list.query()).

Comment on lines +314 to 332
project: t.router({
list: listProjects,
create: createProject,
}),
domain: t.router({
list: listDomains,
}),
deployment: t.router({
list: listDeployments,
search: searchDeployments,
getOpenApiDiff: getOpenApiDiff,
buildLogs: getDeploymentBuildLogs,
}),
environment: t.router({
list: listEnvironments,
}),
environmentVariables: t.router({
list: getEnvs,
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify client code is updated for the new router paths

The router structure has changed significantly from nested paths (deploy.project.*) to flat paths (project.*, deployment.*, etc.). Ensure all client code has been updated.


🏁 Script executed:

#!/bin/bash
# Check for any remaining references to the old nested router structure
echo "Checking for old router path references..."
rg -n "trpc\.(deploy\.project|deploy\.deployment)" --type ts --type tsx
rg -n "useQuery.*deploy\.(project|deployment)" --type ts --type tsx
rg -n "useMutation.*deploy\.(project|deployment)" --type ts --type tsx

Length of output: 126


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Searching for old nested router references (avoid --type flags that caused 'unrecognized file type')..."

patterns=(
  'trpc\.deploy\.(project|deployment)'
  '\bdeploy\.project\b'
  '\bdeploy\.deployment\b'
  'trpc\.(deploy\.)?(project|deployment)'
  'use(Query|Mutation).*trpc\.deploy\.(project|deployment)'
)

if command -v rg >/dev/null 2>&1; then
  RG_BASE=(rg -n --hidden --no-ignore-vcs -S -g '!node_modules/**' -g '**/*.ts' -g '**/*.tsx' -g '**/*.js' -g '**/*.jsx')
  for p in "${patterns[@]}"; do
    echo "==== Pattern: $p ===="
    "${RG_BASE[@]}" "$p" || true
  done

  echo "==== Also searching markdown/json/html/yaml files ===="
  rg -n --hidden --no-ignore-vcs -S -g '!node_modules/**' -g '**/*.{md,json,html,yml,yaml}' 'deploy\.project|deploy\.deployment' || true
else
  echo "rg not available; falling back to git grep"
  for p in "${patterns[@]}"; do
    echo "==== Pattern: $p ===="
    git grep -n -E -- "$p" -- '*.ts' '*.tsx' '*.js' '*.jsx' || true
  done
  echo "==== Also searching other file extensions (fallback) ===="
  git grep -n -E -- 'deploy\.project|deploy\.deployment' -- '*.md' '*.json' '*.html' '*.yml' '*.yaml' || true
fi

echo "Done."

Length of output: 4456


Client code mostly migrated — remove leftover commented trpc.deploy. references*

Active client calls use the new top-level routers (trpc.project., trpc.deployment.). Found leftover commented/legacy references to trpc.deploy.* — update or remove them.

Files with leftover references:

  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx: lines 22-23
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx: lines 33, 72
  • apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx: line 16
  • apps/dashboard/lib/collections/domains.ts: lines 28, 51
  • apps/dashboard/lib/collections/environments.ts: lines 27, 50
  • apps/dashboard/lib/collections/projects.ts: line 55
  • apps/dashboard/lib/collections/deployments.ts: lines 52, 75

Examples confirming migration (no action required):

  • apps/dashboard/app/(app)/projects/[projectId]/deployments/.../deployment-list-search/index.tsx (trpc.deployment.search)
  • apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts (trpc.project.create)
  • apps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsx (trpc.deployment.getOpenApiDiff)
  • apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsx (trpc.deployment.buildLogs)
🤖 Prompt for AI Agents
In apps/dashboard/lib/trpc/routers/index.ts around lines 314-332, the
project/deployment routers have been promoted to top-level (trpc.project.*,
trpc.deployment.*) but there are leftover commented/legacy trpc.deploy.*
references across the codebase; update those references to the new top-level
routers or remove the commented lines. Specifically edit the listed files to
replace trpc.deploy.* with the appropriate trpc.deployment.* (or trpc.project.*
where relevant), adjust any imports/types if needed, and delete stale commented
calls:
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsx
(lines 22-23),
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsx
(lines 33,72),
apps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsx
(line 16), apps/dashboard/lib/collections/domains.ts (lines 28,51),
apps/dashboard/lib/collections/environments.ts (lines 27,50),
apps/dashboard/lib/collections/projects.ts (line 55),
apps/dashboard/lib/collections/deployments.ts (lines 52,75); run a repo-wide
search for "trpc.deploy" to catch any remaining occurrences and ensure all
client calls use trpc.project.* or trpc.deployment.* as appropriate.

Comment on lines +9 to +30
.query(async ({ ctx }) => {
return await db.query.projects
.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
name: true,
slug: true,
updatedAt: true,
gitRepositoryUrl: true,
activeDeploymentId: true,
},
})
.catch((error) => {
console.error("Error querying projects:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Failed to retrieve projects due to an error. If this issue persists, please contact support.",
});
});
});
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)

Prefer try/catch; add deterministic ordering.

Align error handling style and return results consistently ordered (e.g., most recent first).

Apply:

 export const listProjects = t.procedure
   .use(requireUser)
   .use(requireWorkspace)
   .use(withRatelimit(ratelimit.read))
   .query(async ({ ctx }) => {
-    return await db.query.projects
-      .findMany({
-        where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
-        columns: {
-          id: true,
-          name: true,
-          slug: true,
-          updatedAt: true,
-          gitRepositoryUrl: true,
-          activeDeploymentId: true,
-        },
-      })
-      .catch((error) => {
-        console.error("Error querying projects:", error);
-        throw new TRPCError({
-          code: "INTERNAL_SERVER_ERROR",
-          message:
-            "Failed to retrieve projects due to an error. If this issue persists, please contact support.",
-        });
-      });
+    try {
+      return await db.query.projects.findMany({
+        where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
+        columns: {
+          id: true,
+          name: true,
+          slug: true,
+          updatedAt: true,
+          gitRepositoryUrl: true,
+          activeDeploymentId: true,
+        },
+        orderBy: (table, { desc }) => desc(table.updatedAt),
+      });
+    } catch (error) {
+      console.error("Error querying projects:", error);
+      throw new TRPCError({
+        code: "INTERNAL_SERVER_ERROR",
+        message:
+          "Failed to retrieve projects due to an error. If this issue persists, please contact support.",
+      });
+    }
   });
📝 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
.query(async ({ ctx }) => {
return await db.query.projects
.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
name: true,
slug: true,
updatedAt: true,
gitRepositoryUrl: true,
activeDeploymentId: true,
},
})
.catch((error) => {
console.error("Error querying projects:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Failed to retrieve projects due to an error. If this issue persists, please contact support.",
});
});
});
.query(async ({ ctx }) => {
try {
return await db.query.projects.findMany({
where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id),
columns: {
id: true,
name: true,
slug: true,
updatedAt: true,
gitRepositoryUrl: true,
activeDeploymentId: true,
},
orderBy: (table, { desc }) => desc(table.updatedAt),
});
} catch (error) {
console.error("Error querying projects:", error);
throw new TRPCError({
code: "INTERNAL_SERVER_ERROR",
message:
"Failed to retrieve projects due to an error. If this issue persists, please contact support.",
});
}
});
🤖 Prompt for AI Agents
In apps/dashboard/lib/trpc/routers/project/list.ts around lines 9 to 30, replace
the promise .catch chain with an explicit try/catch block around the
db.query.projects.findMany call and in the catch rethrow a TRPCError (code:
"INTERNAL_SERVER_ERROR") with the same user-facing message; additionally add a
deterministic orderBy clause to the query (e.g., order by updatedAt descending
to return most recent first) and return the result from the try block so callers
receive the ordered list.

Comment on lines +19 to 22
// this is likely temporary but we need a way to point to the current prod deployment.
// in the future I think we want to have a special deployment per environment, but for now this is fine
activeDeploymentId: varchar("active_deployment_id", { length: 256 }),

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)

Index (and optional relation) for activeDeploymentId.

This field will be used for lookups/joins; add an index to avoid table scans. Consider a relation to deployments for type safety.

Apply:

 export const projects = mysqlTable(
   "projects",
   {
@@
-    activeDeploymentId: varchar("active_deployment_id", { length: 256 }),
+    activeDeploymentId: varchar("active_deployment_id", { length: 256 }),
@@
   },
   (table) => ({
     workspaceIdx: index("workspace_idx").on(table.workspaceId),
     workspaceSlugIdx: uniqueIndex("workspace_slug_idx").on(table.workspaceId, table.slug),
+    activeDeploymentIdx: index("active_deployment_idx").on(table.activeDeploymentId),
   }),
 );
📝 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
// this is likely temporary but we need a way to point to the current prod deployment.
// in the future I think we want to have a special deployment per environment, but for now this is fine
activeDeploymentId: varchar("active_deployment_id", { length: 256 }),
export const projects = mysqlTable(
"projects",
{
// this is likely temporary but we need a way to point to the current prod deployment.
// in the future I think we want to have a special deployment per environment, but for now this is fine
activeDeploymentId: varchar("active_deployment_id", { length: 256 }),
},
(table) => ({
workspaceIdx: index("workspace_idx").on(table.workspaceId),
workspaceSlugIdx: uniqueIndex("workspace_slug_idx").on(table.workspaceId, table.slug),
activeDeploymentIdx: index("active_deployment_idx").on(table.activeDeploymentId),
}),
);
🤖 Prompt for AI Agents
In internal/db/src/schema/projects.ts around lines 19 to 22, add a database
index (and optionally a foreign key relation) for activeDeploymentId to avoid
table scans on lookups/joins and improve type safety; update the schema to
create an index on "active_deployment_id" and, if your schema/ORM supports it,
declare a relation referencing the deployments table (e.g., deployments.id) so
TypeScript types reflect the relation; ensure the corresponding migration is
added to create the index/constraint and update any model types or imports that
rely on the new relation.

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.

Review continued from previous batch...

Comment on lines +13 to 16
const project = trpc.project.create.useMutation({
onSuccess(data) {
trpcUtils.deploy.project.list.invalidate();
onSuccess(data);
},
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Refresh collections after create (or route creation through collections).

After migrating lists to React‑DB collections, creating via a bare tRPC mutation won’t update the client cache unless you invalidate the collection query or use the collection’s onInsert path. Either:

  • call the projects collection’s invalidate/refresh in onSuccess, or
  • replace the tRPC mutation with collection.projects.insert for optimistic UI and automatic reconciliation.

If preferred, I can draft the change once you confirm the projects collection API (invalidate/refresh/insert) surface.

Also applies to: 18-49


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "=== show file ==="
if [ -f "apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts" ]; then
  sed -n '1,240p' "apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts"
else
  echo "file not found"
fi
echo "=== rg searches ==="
rg -n --hidden --no-ignore -S "collection" || true
rg -n --hidden --no-ignore -S "collections" || true
rg -n --hidden --no-ignore -S "projects" || true
rg -n --hidden --no-ignore -S "trpc.project.create" || true
rg -n --hidden --no-ignore -S "trpc.project" || true
rg -n --hidden --no-ignore -S "collection.projects" || true
rg -n --hidden --no-ignore -S "onInsert" || true
rg -n --hidden --no-ignore -S "insert(" || true
rg -n --hidden --no-ignore -S "invalidate" || true
rg -n --hidden --no-ignore -S "useMutation" || true
echo "=== package.json (partial) ==="
if [ -f package.json ]; then
  jq '.dependencies, .devDependencies' package.json || true
else
  echo "package.json not found"
fi

Length of output: 98271


Refresh projects collection after create (use collection insert or invalidate).

Creating via trpc.project.create.useMutation (apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts:13) does not update the React‑DB projects collection used by project lists. Either:

  • refresh/invalidate the projects collection query in onSuccess (or call the collections reset helper), or
  • replace the tRPC mutation with collection.projects.insert(...) so the collection's onInsert runs and the UI updates optimistically (projects collection: apps/dashboard/lib/collections/projects.ts).
🤖 Prompt for AI Agents
In
apps/dashboard/app/(app)/projects/_components/create-project/use-create-project.ts
around lines 13-16 the trpc.project.create.useMutation onSuccess does not update
the React-DB projects collection, so project lists won't refresh; either update
the onSuccess to refresh the projects collection (e.g., call the collections
reset helper or invalidate/refresh the projects query so the collection reloads)
or replace the tRPC mutation with a call to collection.projects.insert(...) so
the collection's onInsert handler runs and the UI updates optimistically;
implement one of these fixes in the mutation onSuccess path to ensure the
projects collection is updated after create.

Comment on lines +51 to +135
const deployments = useLiveQuery(
(q) => {
// Query filtered environments
// further down below we use this to rightJoin with deployments to filter deployments by environment
let environments = q.from({ environment: collection.environments });

for (const filter of filters) {
if (filter.field === "environment") {
environments = environments.where(({ environment }) =>
eq(environment.slug, filter.value),
);
}
}

let query = q
.from({ deployment: collection.deployments })

.where(({ deployment }) => eq(deployment.projectId, projectId));

// add additional where clauses based on filters.
// All of these are a locical AND

const groupedFilters = filters.reduce(
(acc, f) => {
if (!acc[f.field]) {
acc[f.field] = [];
}
acc[f.field].push(f.value);
return acc;
},
{} as Record<DeploymentListFilterField, (string | number)[]>,
);
for (const [field, values] of Object.entries(groupedFilters)) {
// this is kind of dumb, but `or`s type doesn't allow spreaded args without
// specifying the first two
const [v1, v2, ...rest] = values;
const f = field as DeploymentListFilterField; // I want some typesafety
switch (f) {
case "status":
query = query.where(({ deployment }) =>
or(
eq(deployment.status, v1),
eq(deployment.status, v2),
...rest.map((value) => eq(deployment.status, value)),
),
);
break;
case "branch":
query = query.where(({ deployment }) =>
or(
eq(deployment.gitBranch, v1),
eq(deployment.gitBranch, v2),
...rest.map((value) => eq(deployment.gitBranch, value)),
),
);
break;
case "environment":
// We already filtered
break;
case "since":
query = query.where(({ deployment }) =>
gt(deployment.createdAt, Date.now() - ms(values.at(0) as string)),
);

break;
case "startTime":
query = query.where(({ deployment }) => gte(deployment.createdAt, values.at(0)));
break;
case "endTime":
query = query.where(({ deployment }) => lte(deployment.createdAt, values.at(0)));
break;
default:
break;
}
}

return query
.rightJoin({ environment: environments }, ({ environment, deployment }) =>
eq(environment.id, deployment.environmentId),
)
.orderBy(({ deployment }) => deployment.createdAt, "desc")
.limit(100);
},
[projectId, filters],
);
Copy link
Contributor

Choose a reason for hiding this comment

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

💡 Verification agent

💡 Verification agent

🧩 Analysis chain

Verify environment filter logic and consider extracting filter builder

The filter building logic is complex with nested loops and type assertions. Consider extracting this into a separate function for better testability and maintainability.


🏁 Script executed:

#!/bin/bash
# Verify the environment filter is properly implemented
echo "Checking environment collection structure..."
ast-grep --pattern 'export const environments = $_'

# Check if there are any other components using similar filter patterns
echo "Checking for similar filter patterns..."
rg -n "groupedFilters.*reduce" --type ts --type tsx

Length of output: 5210


Fix environment filter logic and extract filter builder

  • Replace rightJoin(...) with innerJoin(...) — rightJoin makes environment required and deployment optional, producing environment-only rows.
  • Apply OR for multiple environment filters (current loop chains .where() and ANDs distinct slugs); move environment handling into groupedFilters or build OR semantics.
  • Avoid passing undefined into or(...): handle single-value cases (use eq(...) when values.length === 1).
  • Extract the whole filter-building logic into a helper for testability and maintainability.

apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx — lines 51–135.

Comment on lines +9 to 16
// biome-ignore lint/style/noNonNullAssertion: shut up nextjs
const { projectId } = useParams<{ projectId: string }>()!;
return (
<div className="flex flex-col">
<DeploymentsListControls />
<DeploymentsListControlCloud />
<DeploymentsList />
<DeploymentsList projectId={projectId} />
</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)

Avoid non‑null assertion; guard params in a client component.

Non‑null assertion plus “shut up nextjs” comment is brittle and unprofessional. In a client component you can guard and conditionally render.

Apply this diff:

-import { useParams } from "next/navigation";
+import { useParams } from "next/navigation";
@@
-  // biome-ignore lint/style/noNonNullAssertion: shut up nextjs
-  const { projectId } = useParams<{ projectId: string }>()!;
+  const params = useParams<{ projectId?: string }>();
+  const projectId = params?.projectId;
@@
-      <DeploymentsList projectId={projectId} />
+      {projectId ? <DeploymentsList projectId={projectId} /> : null}
📝 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
// biome-ignore lint/style/noNonNullAssertion: shut up nextjs
const { projectId } = useParams<{ projectId: string }>()!;
return (
<div className="flex flex-col">
<DeploymentsListControls />
<DeploymentsListControlCloud />
<DeploymentsList />
<DeploymentsList projectId={projectId} />
</div>
import { useParams } from "next/navigation";
const params = useParams<{ projectId?: string }>();
const projectId = params?.projectId;
return (
<div className="flex flex-col">
<DeploymentsListControls />
<DeploymentsListControlCloud />
{projectId ? <DeploymentsList projectId={projectId} /> : null}
</div>
🤖 Prompt for AI Agents
In apps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsx around
lines 9 to 16, remove the non-null assertion on useParams and instead guard the
params in the client component: call const params = useParams() and check if
params?.projectId exists; if missing return a fallback (null, a spinner, or an
error message) and only render DeploymentsListControls,
DeploymentsListControlCloud and <DeploymentsList projectId={params.projectId} />
when projectId is present. Ensure no "shut up nextjs" comment or bang operator
remains and keep the component a safe, conditionally-rendering client component.

@ogzhanolguncu
Copy link
Contributor

@chronark lets just skip coderabbit comments and merge. I'll check everything out to see if everything is okay.

@chronark chronark merged commit 2b24b32 into main Sep 15, 2025
18 checks passed
@chronark chronark deleted the refactor_migrate_projects_to_TanStack_React_DB branch September 15, 2025 13:52
@coderabbitai coderabbitai bot mentioned this pull request Oct 8, 2025
17 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants