Repository: trycompai/comp
Pull request #467: Claudio/comp 106 add implementation widget
Author: login: claudfuen
Created: 2025-04-29T16:12:11Z | Merged: 2025-04-29T16:12:38Z

## What does this PR do?

<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

- Fixes #XXXX (GitHub issue number)
- Fixes COMP-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

## Visual Demo (For contributors especially)

A visual demonstration is strongly recommended, for both the original and new change **(video / image - any one)**.

#### Video Demo (if applicable):

- Show screen recordings of the issue or feature.
- Demonstrate how to reproduce the issue, the behavior before and after the change.

#### Image Demo (if applicable):

- Add side-by-side screenshots of the original and updated change.
- Highlight any significant change(s).

## Mandatory Tasks (DO NOT REMOVE)

- [ ] I have self-reviewed the code (A decent size PR without self-review might be rejected).
- [ ] I have updated the developer docs in /docs if this PR makes changes that would require a [documentation change](https://trycomp.ai/docs). If N/A, write N/A here and check the checkbox.
- [ ] I confirm automated tests are in place that prove my fix is effective or that my feature works.

## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->

- Are there environment variables that should be set?
- What are the minimal test data to have?
- What is expected (happy path) to have (input and output)?
- Any other important info that could help to test that PR

## Checklist

<!-- Remove bullet points below that don't apply to you -->

- I haven't read the [contributing guide](https://github.com/trycomp/comp/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my changes generate no new warnings



Diff:
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/actions.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/actions.ts
new file mode 100644
index 0000000000..263532cc49
--- /dev/null
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/actions.ts
@@ -0,0 +1,32 @@
+"use server";
+
+import { db } from "@comp/db";
+import { generateChecklistItems } from "./checklist-items";
+import type { ChecklistItemProps } from "./types";
+
+export interface OnboardingStatus {
+	checklistItems: ChecklistItemProps[];
+	completedItems: number;
+	totalItems: number;
+}
+
+export async function getOnboardingStatus(
+	orgId: string,
+): Promise<OnboardingStatus | { error: string }> {
+	const onboarding = await db.onboarding.findUnique({
+		where: {
+			organizationId: orgId,
+		},
+	});
+
+	if (!onboarding) {
+		return { error: "Organization onboarding not found" };
+	}
+
+	const checklistItems = generateChecklistItems(onboarding, orgId);
+
+	const completedItems = checklistItems.filter((item) => item.completed).length;
+	const totalItems = checklistItems.length;
+
+	return { checklistItems, completedItems, totalItems };
+} 
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/checklist-items.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/checklist-items.tsx
new file mode 100644
index 0000000000..69e3a7721a
--- /dev/null
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/checklist-items.tsx
@@ -0,0 +1,67 @@
+import type { Onboarding } from "@comp/db/types";
+import { Icons } from "@comp/ui/icons";
+import { ListCheck, NotebookText, Store, Users } from "lucide-react";
+import type { ChecklistItemProps } from "./types";
+
+export function generateChecklistItems(
+	onboarding: Onboarding,
+	orgId: string,
+): ChecklistItemProps[] {
+	return [
+		{
+			title: "Check & Publish Policies",
+			description:
+				"We've given you all of the policies you need to get started. Go through them, make sure they're relevant to your organization and then publish them for your employees to sign.",
+			href: `/${orgId}/policies`,
+			dbColumn: "policies",
+			completed: onboarding.policies,
+			docs: "https://trycomp.ai/docs/policies",
+			buttonLabel: "Publish Policies",
+			icon: <NotebookText className="h-5 w-5" />,
+		},
+		{
+			title: "Add Employees",
+			description:
+				"You should add all of your employees to Comp AI, either through an integration or by manually adding them and then ask them to sign the policies you published in the employee portal.",
+			href: `/${orgId}/people`,
+			dbColumn: "employees",
+			completed: onboarding.employees,
+			docs: "https://trycomp.ai/docs/people",
+			buttonLabel: "Add an Employee",
+			icon: <Users className="h-5 w-5" />,
+		},
+		{
+			title: "Add Vendors",
+			description:
+				"For frameworks like SOC 2, you must assess and report on your vendors. You can add your vendors to Comp AI, and assign risk levels to them. Auditors can review the vendors and their risk levels.",
+			href: `/${orgId}/vendors`,
+			dbColumn: "vendors",
+			completed: onboarding.vendors,
+			docs: "https://trycomp.ai/docs/vendors",
+			buttonLabel: "Add a Vendor",
+			icon: <Store className="h-5 w-5" />,
+		},
+		{
+			title: "Manage Risks",
+			description:
+				"You can manage your risks in Comp AI by adding them to your organization and then assigning them to employees or vendors. Auditors can review the risks and their status.",
+			href: `/${orgId}/risk`,
+			dbColumn: "risk",
+			completed: onboarding.risk,
+			docs: "https://trycomp.ai/docs/risks",
+			buttonLabel: "Create a Risk",
+			icon: <Icons.Risk className="h-5 w-5" />,
+		},
+		{
+			title: "Complete Tasks",
+			description:
+				"Tasks in Comp AI are automatically generated for you, based on the frameworks you selected. Tasks are linked to controls, which are determined by your policies. By completing tasks, you can show auditors that you are following your own policies.",
+			href: `/${orgId}/tasks`,
+			dbColumn: "tasks",
+			completed: onboarding.tasks,
+			docs: "https://trycomp.ai/docs/tasks",
+			buttonLabel: "Create a Task",
+			icon: <ListCheck className="h-5 w-5" />,
+		},
+	];
+} 
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/components/Checklist.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/components/Checklist.tsx
index 4432f85a0d..028b890193 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/components/Checklist.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/components/Checklist.tsx
@@ -1,13 +1,13 @@
 "use client";
 
-import { ChecklistProps } from "../types/ChecklistProps.types";
+import { ChecklistItemProps } from "../types";
 import { ChecklistItem } from "./ChecklistItem";
 
-export function Checklist({ items }: ChecklistProps) {
+export function Checklist({ items }: { items: ChecklistItemProps[] }) {
     return (
         <div className="flex flex-col gap-4">
             {items.map((item) => (
-                <ChecklistItem key={item.dbColumn as string} {...item} />
+                <ChecklistItem key={item.dbColumn} {...item} />
             ))}
         </div>
     );
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/page.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/page.tsx
index 9eb6b45270..c063506408 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/page.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/page.tsx
@@ -1,103 +1,7 @@
-import { db } from "@comp/db";
-import { Icons } from "@comp/ui/icons";
-import { ListCheck, NotebookText, Store, Users } from "lucide-react";
 import { redirect } from "next/navigation";
-import { cache } from "react";
 import { Checklist } from "./components/Checklist";
 import { OnboardingProgress } from "./components/OnboardingProgress";
-import { ChecklistItemProps } from "./types/ChecklistProps.types";
-
-const getChecklistItems = cache(
-	async (
-		orgId: string,
-	): Promise<
-		| {
-				checklistItems: ChecklistItemProps[];
-				completedItems: number;
-				totalItems: number;
-		  }
-		| { error: string }
-	> => {
-		const onboarding = await db.onboarding.findUnique({
-			where: {
-				organizationId: orgId,
-			},
-		});
-
-		if (!onboarding) {
-			return { error: "Organization onboarding not found" };
-		}
-
-		const checklistItems: ChecklistItemProps[] = [
-			{
-				title: "Check & Publish Policies",
-				description:
-					"We've given you all of the policies you need to get started. Go through them, make sure they're relevant to your organization and then publish them for your employees to sign.",
-				href: `/${orgId}/policies`,
-				dbColumn: "policies",
-				completed: onboarding.policies,
-				docs: "https://trycomp.ai/docs/policies",
-				buttonLabel: "Publish Policies",
-				icon: <NotebookText className="h-5 w-5" />,
-			},
-			{
-				title: "Add Employees",
-				description:
-					"You should add all of your employees to Comp AI, either through an integration or by manually adding them and then ask them to sign the policies you published in the employee portal.",
-				href: `/${orgId}/people`,
-				dbColumn: "employees",
-				completed: onboarding.employees,
-				docs: "https://trycomp.ai/docs/people",
-				buttonLabel: "Add an Employee",
-				icon: <Users className="h-5 w-5" />,
-			},
-			{
-				title: "Add Vendors",
-				description:
-					"For frameworks like SOC 2, you must assess and report on your vendors. You can add your vendors to Comp AI, and assign risk levels to them. Auditors can review the vendors and their risk levels.",
-				href: `/${orgId}/vendors`,
-				dbColumn: "vendors",
-				completed: onboarding.vendors,
-				docs: "https://trycomp.ai/docs/vendors",
-				buttonLabel: "Add a Vendor",
-				icon: <Store className="h-5 w-5" />,
-			},
-			{
-				title: "Manage Risks",
-				description:
-					"You can manage your risks in Comp AI by adding them to your organization and then assigning them to employees or vendors. Auditors can review the risks and their status.",
-				href: `/${orgId}/risk`,
-				dbColumn: "risk",
-				completed: onboarding.risk,
-				docs: "https://trycomp.ai/docs/risks",
-				buttonLabel: "Create a Risk",
-				icon: <Icons.Risk className="h-5 w-5" />,
-			},
-			{
-				title: "Complete Tasks",
-				description:
-					"Tasks in Comp AI are automatically generated for you, based on the frameworks you selected. Tasks are linked to controls, which are determined by your policies. By completing tasks, you can show auditors that you are following your own policies.",
-				href: `/${orgId}/tasks`,
-				dbColumn: "tasks",
-				completed: onboarding.tasks,
-				docs: "https://trycomp.ai/docs/tasks",
-				buttonLabel: "Create a Task",
-				icon: <ListCheck className="h-5 w-5" />,
-			},
-		];
-
-		const completedItems = checklistItems.filter(
-			(item) => item.completed,
-		).length;
-		const totalItems = checklistItems.length;
-
-		if (completedItems === totalItems) {
-			return redirect(`/${orgId}/frameworks`);
-		}
-
-		return { checklistItems, completedItems, totalItems };
-	},
-);
+import { getOnboardingStatus } from "./actions";
 
 export default async function Page({
 	params,
@@ -105,10 +9,10 @@ export default async function Page({
 	params: Promise<{ orgId: string }>;
 }) {
 	const { orgId } = await params;
-	const checklistData = await getChecklistItems(orgId);
+	const checklistData = await getOnboardingStatus(orgId);
 
 	if ("error" in checklistData) {
-		return <div>{checklistData.error}</div>;
+		return <div>Error loading onboarding status: {checklistData.error}</div>;
 	}
 
 	return (
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types/ChecklistProps.types.ts b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types.ts
similarity index 60%
rename from apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types/ChecklistProps.types.ts
rename to apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types.ts
index 35dd62bc1f..57f8d7b2a5 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types/ChecklistProps.types.ts
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types.ts
@@ -1,16 +1,12 @@
-import { Onboarding } from "@comp/db/types";
-
-export interface ChecklistProps {
-	items: ChecklistItemProps[];
-}
+import type { Onboarding } from "@comp/db/types";
 
 export interface ChecklistItemProps {
 	title: string;
-	description?: string;
+	description: string;
 	href: string;
-	docs: string;
 	dbColumn: Exclude<keyof Onboarding, "organizationId">;
 	completed: boolean;
+	docs: string;
 	buttonLabel: string;
 	icon: React.ReactNode;
-}
+} 
\ No newline at end of file
diff --git a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx
index 7309231b56..0f293ad869 100644
--- a/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx
+++ b/apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx
@@ -9,6 +9,8 @@ import type { Organization } from "@comp/db/types";
 import dynamic from "next/dynamic";
 import { cookies, headers } from "next/headers";
 import { notFound, redirect } from "next/navigation";
+import { FloatingOnboardingChecklist } from "@/components/onboarding/FloatingOnboardingChecklist";
+import { getOnboardingStatus } from "./implementation/actions";
 
 const HotKeys = dynamic(
 	() => import("@/components/hot-keys").then((mod) => mod.HotKeys),
@@ -62,6 +64,16 @@ export default async function Layout({
 		return notFound();
 	}
 
+	// Fetch onboarding status
+	const onboardingStatus = await getOnboardingStatus(currentOrganization.id);
+
+	// Handle potential error during fetching
+	if ("error" in onboardingStatus) {
+		// Decide how to handle the error - maybe log it or show a generic error message
+		// For now, we'll proceed without the checklist
+		console.error("Error fetching onboarding status:", onboardingStatus.error);
+	}
+
 	return (
 		<SidebarProvider initialIsCollapsed={isCollapsed}>
 			<AnimatedLayout
@@ -72,6 +84,16 @@ export default async function Layout({
 				<main className="px-4 mx-auto pb-8">{children}</main>
 				<AssistantSheet />
 			</AnimatedLayout>
+			{/* Render floating checklist only if onboarding is not complete and no fetch error */}
+			{!("error" in onboardingStatus) &&
+				onboardingStatus.completedItems < onboardingStatus.totalItems && (
+					<FloatingOnboardingChecklist
+						orgId={currentOrganization.id}
+						completedItems={onboardingStatus.completedItems}
+						totalItems={onboardingStatus.totalItems}
+						checklistItems={onboardingStatus.checklistItems}
+					/>
+				)}
 			<HotKeys />
 		</SidebarProvider>
 	);
diff --git a/apps/app/src/components/onboarding/FloatingOnboardingChecklist.tsx b/apps/app/src/components/onboarding/FloatingOnboardingChecklist.tsx
new file mode 100644
index 0000000000..1116c4750c
--- /dev/null
+++ b/apps/app/src/components/onboarding/FloatingOnboardingChecklist.tsx
@@ -0,0 +1,77 @@
+'use client';
+
+import { Progress } from "@comp/ui/progress";
+import { cn } from "@comp/ui/cn";
+import Link from "next/link";
+import { Button } from "@comp/ui/button";
+import type { ChecklistItemProps } from "@/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types";
+import { Circle } from "lucide-react";
+import { usePathname } from "next/navigation";
+
+interface FloatingOnboardingChecklistProps {
+	orgId: string;
+	completedItems: number;
+	totalItems: number;
+	checklistItems: ChecklistItemProps[];
+	className?: string;
+}
+
+export function FloatingOnboardingChecklist({
+	orgId,
+	completedItems,
+	totalItems,
+	checklistItems,
+	className,
+}: FloatingOnboardingChecklistProps) {
+	const progressPercentage = (completedItems / totalItems) * 100;
+	const remainingItems = totalItems - completedItems;
+	const incompleteItems = checklistItems.filter((item) => !item.completed);
+	const pathname = usePathname();
+
+	const implementationPathRegex = /\/[^/]+\/implementation$/;
+
+	if (remainingItems === 0 || implementationPathRegex.test(pathname)) {
+		return null;
+	}
+
+	return (
+		<div
+			className={cn(
+				"fixed bottom-4 right-4 z-50 w-80 rounded-sm border bg-card p-4 text-card-foreground shadow-lg",
+				className,
+			)}
+		>
+			<div className="mb-3">
+				<h4 className="mb-1 font-medium leading-none">Implementation Progress</h4>
+				<div className="flex justify-between text-xs text-muted-foreground mb-2">
+					<span>{completedItems} Completed</span>
+					<span>{remainingItems} Remaining</span>
+				</div>
+				<Progress value={progressPercentage} className="h-1.5" />
+			</div>
+
+			<div className="mb-3 grid gap-3 max-h-40 overflow-y-auto">
+				{incompleteItems.map((item) => (
+					<Link
+						href={item.href}
+						key={item.dbColumn}
+						className="group flex items-center gap-2 text-sm hover:text-primary"
+					>
+						<Circle className="h-4 w-4 text-muted-foreground group-hover:text-primary" />
+						<span className="font-medium">{item.title}</span>
+					</Link>
+				))}
+			</div>
+
+			<Link href={`/${orgId}/implementation`} passHref>
+				<Button
+					variant="secondary"
+					size="sm"
+					className="w-full"
+				>
+					View All Steps
+				</Button>
+			</Link>
+		</div>
+	);
+} 
\ No newline at end of file


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

This pull request refactors the routing paths by replacing "/employees" with "/people" and integrates an onboarding checklist widget via new actions and UI components. Key changes include:
- Updating revalidation paths across multiple action files and components.
- Introducing a new onboarding checklist with corresponding types, pages, and actions.
- Removing obsolete "employees/all" pages and components.

### Reviewed Changes

Copilot reviewed 57 out of 57 changed files in this pull request and generated 2 comments.

<details>
<summary>Show a summary per file</summary>

| File | Description |
| ---- | ----------- |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/update-employee.ts | Updated revalidation paths from "/employees" to "/people". |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/update-employee-status.ts | Updated revalidation paths to reflect new routing. |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/update-employee-details.ts | Revalidation paths updated to "/people". |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/people/[employeeId]/actions/update-department.ts | Revalidation paths updated to "/people". |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx | Added onboarding checklist logic with error logging for fetching onboarding status. |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/types.ts | Updated the ChecklistItemProps by making description required. |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/page.tsx | Updated onboarding status retrieval and enhanced error messaging in the UI. |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/components/Checklist.tsx | Modified type import and key usage for Checklist items. |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/checklist-items.tsx | New file generating checklist items with updated routing. |
| apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/actions.ts | New file that consolidates onboarding status logic. |
| apps/app/src/actions/organization/remove-employee.ts and invite-employee.ts | Updated revalidation paths to remove "/employees/all" references. |
| Other employees/all pages/components | Removed obsolete files related to the "employees/all" route. |
</details>



<details>
<summary>Comments suppressed due to low confidence (1)</summary>

**apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/components/Checklist.tsx:10**
* [nitpick] Using 'dbColumn' as a React key might lead to duplicate keys if its values are not guaranteed to be unique; consider using a more unique identifier if available.
```
<ChecklistItem key={item.dbColumn} {...item} />
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-29T16:13:09Z

Review comments:
diff_hunk: @@ -62,6 +64,16 @@ export default async function Layout({
 		return notFound();
 	}
 
+	// Fetch onboarding status
+	const onboardingStatus = await getOnboardingStatus(currentOrganization.id);
+
+	// Handle potential error during fetching
+	if ("error" in onboardingStatus) {
+		// Decide how to handle the error - maybe log it or show a generic error message
+		// For now, we'll proceed without the checklist
+		console.error("Error fetching onboarding status:", onboardingStatus.error);
path: apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/layout.tsx
user: login: Copilot
body: [nitpick] While console.error is useful for debugging during development, integrating a structured error tracking or user feedback mechanism may improve error handling in production.
created_at: 2025-04-29T16:13:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,32 @@
+"use server";
+
+import { db } from "@comp/db";
+import { generateChecklistItems } from "./checklist-items";
+import type { ChecklistItemProps } from "./types";
+
+export interface OnboardingStatus {
+	checklistItems: ChecklistItemProps[];
+	completedItems: number;
+	totalItems: number;
+}
+
+export async function getOnboardingStatus(
+	orgId: string,
+): Promise<OnboardingStatus | { error: string }> {
+	const onboarding = await db.onboarding.findUnique({
+		where: {
+			organizationId: orgId,
+		},
+	});
+
+	if (!onboarding) {
+		return { error: "Organization onboarding not found" };
+	}
+
+	const checklistItems = generateChecklistItems(onboarding, orgId);
+
+	const completedItems = checklistItems.filter((item) => item.completed).length;
+	const totalItems = checklistItems.length;
+
+	return { checklistItems, completedItems, totalItems };
path: apps/app/src/app/[locale]/(app)/(dashboard)/[orgId]/implementation/actions.ts
user: login: Copilot
body: [nitpick] The getOnboardingStatus function returns an error object on failure and a status object on success; standardizing error handling (e.g., by throwing exceptions) could avoid ambiguity in downstream consumers.
created_at: 2025-04-29T16:13:09Z
author_association: CONTRIBUTOR

PR comments:
user: login: linear[bot]
created_at: 2025-04-29T16:12:14Z
body: <p><a href="https://linear.app/compai/issue/COMP-106/add-implementation-widget">COMP-106 Add Implementation Widget</a></p>
author_association: NONE
user: login: vercel[bot]
created_at: 2025-04-29T16:12:16Z
body: [vc]: #clO8pfTGo92tX6NwExRi+dADzOw8FvneJyRYwtFzN2w=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJhcHAiLCJyb290RGlyZWN0b3J5IjoiYXBwcy9hcHAiLCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20vY29tcC1haS9hcHAvMjNRUlFoR05wZEpXM25ZVVE3SHlUWUF6NHJRSiIsInByZXZpZXdVcmwiOiIiLCJuZXh0Q29tbWl0U3RhdHVzIjoiRkFJTEVEIiwibGl2ZUZlZWRiYWNrIjp7InJlc29sdmVkIjowLCJ1bnJlc29sdmVkIjowLCJ0b3RhbCI6MCwibGluayI6IiJ9fSx7Im5hbWUiOiJjb21wLXBvcnRhbCIsInJvb3REaXJlY3RvcnkiOiJhcHBzL3BvcnRhbCIsImxpdmVGZWVkYmFjayI6eyJyZXNvbHZlZCI6MCwidW5yZXNvbHZlZCI6MCwidG90YWwiOjAsImxpbmsiOiIifSwiaW5zcGVjdG9yVXJsIjoiaHR0cHM6Ly92ZXJjZWwuY29tL2NvbXAtYWkvY29tcC1wb3J0YWwvQ0VScEZUdTR3OHRNNFdVVXJnWXJZODlpSnE3SCIsInByZXZpZXdVcmwiOiIiLCJuZXh0Q29tbWl0U3RhdHVzIjoiU0tJUFBFRCJ9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **app** | ❌ Failed ([Inspect](https://vercel.com/comp-ai/app/23QRQhGNpdJW3nYUQ7HyTYAz4rQJ)) |  |  | Apr 29, 2025 4:15pm |

<details><summary>1 Skipped Deployment</summary>

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **comp-portal** | ⬜️ Skipped ([Inspect](https://vercel.com/comp-ai/comp-portal/CERpFTu4w8tM4WUUrgYrY89iJq7H)) |  |  | Apr 29, 2025 4:15pm |
</details>



author_association: NONE