diff --git a/frontend/pluto_duck_frontend/app/globals.css b/frontend/pluto_duck_frontend/app/globals.css
index c510cac..de2fef0 100644
--- a/frontend/pluto_duck_frontend/app/globals.css
+++ b/frontend/pluto_duck_frontend/app/globals.css
@@ -274,6 +274,27 @@
animation: stepIn 0.4s cubic-bezier(0.23, 1, 0.32, 1) forwards;
}
+@keyframes cardIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.animate-card-in {
+ animation: cardIn 0.5s cubic-bezier(0.23, 1, 0.32, 1) forwards;
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .animate-card-in {
+ animation: none !important;
+ }
+}
+
.animate-text-glow {
animation: textGlow 2.2s ease-in-out infinite;
}
diff --git a/frontend/pluto_duck_frontend/components/ai-elements/todo-checkbox.tsx b/frontend/pluto_duck_frontend/components/ai-elements/todo-checkbox.tsx
new file mode 100644
index 0000000..62a4c46
--- /dev/null
+++ b/frontend/pluto_duck_frontend/components/ai-elements/todo-checkbox.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import { cn } from "@/lib/utils";
+import { memo, type ComponentProps } from "react";
+import {
+ getTodoCheckboxContainerClass,
+ IN_PROGRESS_TODO_GLYPH,
+ type TodoCheckboxStatus,
+} from "./todoCheckboxModel";
+
+export type TodoCheckboxProps = ComponentProps<"span"> & {
+ status?: TodoCheckboxStatus;
+};
+
+export const TodoCheckbox = memo(
+ ({ status = "pending", className, ...props }: TodoCheckboxProps) => (
+
+ {status === "in_progress" ? (
+
+ {IN_PROGRESS_TODO_GLYPH}
+
+ ) : null}
+ {status === "completed" ? (
+
+ ) : null}
+
+ )
+);
+
+TodoCheckbox.displayName = "TodoCheckbox";
diff --git a/frontend/pluto_duck_frontend/components/ai-elements/todoCheckboxModel.ts b/frontend/pluto_duck_frontend/components/ai-elements/todoCheckboxModel.ts
new file mode 100644
index 0000000..f00892e
--- /dev/null
+++ b/frontend/pluto_duck_frontend/components/ai-elements/todoCheckboxModel.ts
@@ -0,0 +1,15 @@
+export type TodoCheckboxStatus = "pending" | "in_progress" | "completed";
+
+export const IN_PROGRESS_TODO_GLYPH = "\u2734\uFE0E";
+
+export function getTodoCheckboxContainerClass(
+ status: TodoCheckboxStatus
+): string {
+ if (status === "completed") {
+ return "border-0 bg-muted-foreground/60 text-white";
+ }
+ if (status === "in_progress") {
+ return "border-[1.5px] border-muted-foreground/50 bg-transparent";
+ }
+ return "border-[1.5px] border-muted-foreground/30 bg-transparent";
+}
diff --git a/frontend/pluto_duck_frontend/components/chat/renderers/ApprovalRenderer.tsx b/frontend/pluto_duck_frontend/components/chat/renderers/ApprovalRenderer.tsx
index 83d75eb..cdea799 100644
--- a/frontend/pluto_duck_frontend/components/chat/renderers/ApprovalRenderer.tsx
+++ b/frontend/pluto_duck_frontend/components/chat/renderers/ApprovalRenderer.tsx
@@ -24,9 +24,9 @@ export const ApprovalRenderer = memo(function ApprovalRenderer({
const isPending = item.decision === 'pending';
return (
-
+
- Approval
+ Approval
-
{summary}
+
+ {summary}
+
{isPending && (