feat: load deployment details UI from db #3942
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
|
📝 WalkthroughWalkthroughRefactors 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
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
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
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ 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)
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.
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. Comment |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
* 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>
f5dc233 to
64aa43c
Compare
9b3e5bc to
dd64beb
Compare
64aa43c to
84de91f
Compare
|
Thank you for following the naming conventions for pull request titles! 🙏 |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
...ts/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
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 slicesFile: 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 uselogsfor auto‑expandThe
selectabove already returnsLogEntry[]. Drop the extrauseMemoand switch the effect tologs.- 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: Exposeerrorin the hook’s return typeLets 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 failenvironments.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 workspaceSchema 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 CONFLICTBetween 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 loggerLogging 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 slugsUNIQUE(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_idLookups/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 stateWithout 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 commentsKeep 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 nameMirror 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 pathconsole.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 insertchanges 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 mutatePre-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 titleThe “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 unusedEither 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 UXNo 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.validatedQuerytRPC resolvers receive
input; usingctx.validatedQuerywill return undefined unless middleware injects it. Useinput.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 queryPrevents 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 dataAvoid crashes if
projects.datais 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.
@keyframesinside<style jsx>gets scoped; the inlineanimation: "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
safeinstead ofconfig.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/logsis 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 projectaction 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
allSettledand 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 successThe 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
📒 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.tsapps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsxapps/dashboard/lib/trpc/routers/environment/list.tsapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsxapps/dashboard/lib/trpc/routers/domains/list.tsapps/dashboard/lib/trpc/routers/project/list.tsapps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.tsapps/dashboard/lib/collections/deployments.tsapps/dashboard/lib/trpc/routers/deployment/list.tsapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsxapps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/dashboard/lib/trpc/routers/index.tsapps/dashboard/app/(app)/projects/[projectId]/layout.tsxapps/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.tsxapps/dashboard/lib/trpc/routers/environment/list.tsapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsxapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/add-env-var-row.tsxapps/dashboard/lib/trpc/routers/project/list.tsapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/dashboard/lib/trpc/routers/deployment/list.tsapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/dashboard/lib/trpc/routers/index.tsapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/navigations/project-navigation.tsxapps/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.tsxapps/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.tsapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/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)
apps/dashboard/app/(app)/projects/_components/create-project/create-project.schema.ts
Show resolved
Hide resolved
...nts/components/controls/components/deployment-list-filters/components/environment-filter.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
| environment: t.router({ | ||
| list: listEnvironments, | ||
| }), | ||
| environmentVariables: t.router({ | ||
| list: getEnvs, | ||
| }), | ||
| deployment: deploymentRouter, | ||
| }); |
There was a problem hiding this comment.
🧹 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.
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsx
Show resolved
Hide resolved
| 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>, |
There was a problem hiding this comment.
🧹 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.
| 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.
| <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> |
There was a problem hiding this comment.
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.
| <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.
| <span className="text-gray-12 font-medium">20GB</span> | ||
| mb | ||
| </div> |
There was a problem hiding this comment.
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> 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.
| <span className="text-gray-12 font-medium">20GB</span> | |
| mb | |
| </div> | |
| <span className="text-gray-12 font-medium">20</span> 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.
| .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), |
There was a problem hiding this comment.
🧹 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.
| .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.
| const sortedDeployments = deployments.data.sort( | ||
| (a, b) => b.deployment.createdAt - a.deployment.createdAt, | ||
| ); |
There was a problem hiding this comment.
🧹 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.
| 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.
There was a problem hiding this comment.
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.
filtersis captured at render-time; if state changes beforeonSuccessruns, you can overwrite newer user changes. Use a functional updater.- const transformedFilters = transformStructuredOutputToFilters(data, filters); - updateFilters(transformedFilters); + updateFilters((prev) => transformStructuredOutputToFilters(data, prev));If
updateFiltersdoes not accept an updater function, confirm its API and we can adjust the hook to expose one.
27-39: Don’t surface rawerror.messageto 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_atis 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 projectsConfirmed: 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 validatedinput(input.query) —ctx.validatedQueryis 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
allOperatorsis 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
formatfunction 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 safelyThe 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 bleedqueryClient 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 itemsKeying 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 titleCondition 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 unreachabletitle 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 unusedEither 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 metadataLinks/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 existTailwind’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 failurePromise.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 clearedThis addresses the previously raised UX issue about stale titles after clearing filters.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 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.tsapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsxapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsxapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsxapps/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.tsapps/dashboard/lib/trpc/routers/deployment/llm-search/index.tsapps/dashboard/lib/collections/deployments.tsapps/dashboard/lib/trpc/routers/domains/list.tsinternal/db/src/schema/projects.tsapps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsxapps/dashboard/lib/trpc/routers/project/create.tsapps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsxapps/dashboard/lib/trpc/routers/project/list.tsapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/lib/trpc/routers/deployment/list.tsapps/dashboard/app/(app)/projects/[projectId]/layout.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.tsapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsxapps/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.tsapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsxapps/dashboard/lib/trpc/routers/project/create.tsapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/lib/trpc/routers/deployment/list.tsapps/dashboard/app/(app)/projects/[projectId]/layout.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/layout.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/layout.tsxapps/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)
| gitRepositoryUrl: values.gitRepositoryUrl ?? null, | ||
| }); |
There was a problem hiding this comment.
🧹 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.
| 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("")), |
There was a problem hiding this comment.
🧹 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.
| 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.
| const fakeDeploymentId = "idk"; | ||
| router.push(`/projects/${project.id}/deployments/${fakeDeploymentId}/logs`); | ||
| router.push(`/projects/${projectId}/deployments/${fakeDeploymentId}/logs`); |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
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:
- Fetching the active deployment ID for the project
- Changing these routes to not require a deployment ID
- 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.
...ts/[projectId]/deployments/components/controls/components/deployment-list-datetime/index.tsx
Show resolved
Hide resolved
| 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, | ||
| }))} |
There was a problem hiding this comment.
🧹 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.
| 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.", | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 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.
| 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.
| return await db.query.environments.findMany({ | ||
| where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id), | ||
| columns: { | ||
| id: true, | ||
| projectId: true, | ||
| slug: true, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
🧹 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.
| 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.
| 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.", | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 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), |
There was a problem hiding this comment.
🧹 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.
✅ Actions performedReview triggered.
|
| 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"]} |
There was a problem hiding this comment.
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:
- Fetching deployment details for each project and displaying actual values
- Removing these fields from the card if the data is not available
- 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.
| 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"]} |
There was a problem hiding this comment.
💡 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' || trueLength 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 || trueLength 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.
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
| <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 |
There was a problem hiding this comment.
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.
| <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> |
| 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), | ||
| ); |
There was a problem hiding this comment.
🧹 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.
| 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.
| const sortedDeployments = deployments.data.sort( | ||
| (a, b) => b.deployment.createdAt - a.deployment.createdAt, | ||
| ); |
There was a problem hiding this comment.
🧹 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.
| 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.
| 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(); | ||
|
|
There was a problem hiding this comment.
🧹 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.
| deployment.gitCommitSha?.substring(0, 7) || deployment.id.substring(0, 7); | ||
| const branch = deployment.gitBranch || "unknown"; |
There was a problem hiding this comment.
🧹 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.
| 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).
There was a problem hiding this comment.
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-11vstext-grayA-11. If the design system prefers A‑palette for text on translucent backgrounds, align pending’s icon color totext-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 pathinternal/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_atis NOT NULL in the SQL schema; omitting timestamps risks a transaction failure when creating the project.Add timestamps (mirroring
projectsinsert):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 checkThe 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 expansionThe
expandGroupedStatusfunction 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 mappingThe
getColorClassfunction still references "completed" status at line 53, but this status has been removed from bothDEPLOYMENT_STATUSESandGROUPED_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 errorThe 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 definitionOr 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 handlingThe 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 contextThe 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
projectIdprop is defined inAddEnvVarRowPropsbut 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 callersshortenId'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
sortedDeploymentsdirectly sortingdeployments.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
📒 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.tsapps/dashboard/lib/trpc/routers/domains/list.tsapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/hooks/use-deployment-logs.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/components/actions/deployment-list-table-action.popover.constants.tsxapps/dashboard/lib/trpc/routers/project/list.tsapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/dashboard/lib/trpc/routers/deployment/llm-search/index.tsapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/utils/get-row-class.tsapps/dashboard/lib/collections/deployments.tsapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/page.tsxapps/dashboard/lib/trpc/routers/deployment/list.tsapps/dashboard/lib/trpc/routers/index.tsapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/sections.tsxapps/dashboard/app/(app)/projects/[projectId]/layout.tsxapps/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.tsapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/env-var-row.tsxapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/hooks/use-env-var-manager.tsxapps/dashboard/lib/trpc/routers/project/list.tsapps/dashboard/lib/collections/environments.tsapps/dashboard/app/(app)/projects/[projectId]/details/env-variables-section/components/env-var-form.tsxapps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsxapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/dashboard/lib/trpc/routers/deployment/llm-search/index.tsapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsxapps/dashboard/lib/trpc/routers/deployment/list.tsapps/dashboard/lib/trpc/routers/index.tsapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/details/active-deployment-card/index.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/page.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/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.tsapps/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.tsxapps/dashboard/app/(app)/projects/[projectId]/details/project-details-expandables/index.tsxapps/dashboard/app/(app)/projects/_components/list/project-actions.tsxapps/dashboard/app/(app)/projects/[projectId]/page.tsxapps/dashboard/app/(app)/projects/[projectId]/diff/[...compare]/page.tsxapps/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: !!deploymentIdifdeploymentIdcan 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/readychanges, verifydeploymentListFilterFieldConfig.status.getColorClassmaps these to non-default classes.apps/dashboard/lib/collections/ratelimit_overrides.ts (1)
15-18: Type export + generic OK — verify create input doesn't requireid.You're parsing
changesand sending it straight totrpcClient.ratelimit.override.create.mutate(schema.parse(changes)); confirm the server's create procedure does not generate theidserver‑side. If the server generates ids, omitidbefore sending (e.g.schema.omit({ id: true }).parse(changes)or removechanges.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 pipefailecho "PWD: $(pwd)"
echo "=== check ripgrep ==="
if command -v rg >/dev/null 2>&1; then
echo "rg found"
else
echo "rg NOT found"
fiecho "=== 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
fiecho "=== 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"
fiecho "=== 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.
| gitRepositoryUrl: z.string().trim().url("Must be a valid URL").nullable().or(z.literal("")), | ||
| }); |
There was a problem hiding this comment.
🛠️ 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.
| 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.
| 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" |
There was a problem hiding this comment.
🧹 Nitpick (assertive)
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.
| //INFO: This will change soon | ||
| const fakeDeploymentId = "idk"; | ||
| router.push(`/projects/${project.id}/deployments/${fakeDeploymentId}/logs`); | ||
| router.push(`/projects/${projectId}/deployments/${fakeDeploymentId}/logs`); |
There was a problem hiding this comment.
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:
- Fetch the actual active deployment ID for the project
- Navigate to project-level routes that don't require a deployment ID
- 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.
...ects/[projectId]/deployments/components/controls/components/deployment-list-search/index.tsx
Show resolved
Hide resolved
...pp)/projects/[projectId]/deployments/components/table/components/deployment-status-badge.tsx
Show resolved
Hide resolved
| 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, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
💡 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 || trueLength 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" || trueLength 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()).
| 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, | ||
| }), |
There was a problem hiding this comment.
💡 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 tsxLength 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.
| .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.", | ||
| }); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
🧹 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.
| .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.
| // 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 }), | ||
|
|
There was a problem hiding this comment.
🧹 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.
| // 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.
| const project = trpc.project.create.useMutation({ | ||
| onSuccess(data) { | ||
| trpcUtils.deploy.project.list.invalidate(); | ||
| onSuccess(data); | ||
| }, |
There was a problem hiding this comment.
💡 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"
fiLength 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.
| 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], | ||
| ); |
There was a problem hiding this comment.
💡 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 tsxLength 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.
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
apps/dashboard/app/(app)/projects/[projectId]/deployments/components/table/deployments-list.tsx
Show resolved
Hide resolved
| // 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> |
There was a problem hiding this comment.
🧹 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.
| // 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.
|
@chronark lets just skip coderabbit comments and merge. I'll check everything out to see if everything is okay. |

What does this PR do?
skipping this in favour of a quick huddle with oz
Summary by CodeRabbit
New Features
Improvements