Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion src/components/chat/agent-plan-overlay.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { fireEvent, render, screen } from "@testing-library/react"
import { NextIntlClientProvider } from "next-intl"
import { describe, expect, it } from "vitest"
import { afterEach, describe, expect, it } from "vitest"

import { AgentPlanOverlay } from "./agent-plan-overlay"
import enMessages from "@/i18n/messages/en.json"
Expand All @@ -22,6 +22,10 @@ const sampleEntries: PlanEntryInfo[] = [
]

describe("AgentPlanOverlay", () => {
afterEach(() => {
window.localStorage.removeItem("codeg-plan-overlay-auto-expand")
})

it("renders nothing when entries are empty", () => {
const { container } = renderWithIntl(
<AgentPlanOverlay entries={[]} planKey="p-empty" />
Expand Down Expand Up @@ -117,6 +121,37 @@ describe("AgentPlanOverlay", () => {
expect(screen.getByText("Done A")).toBeInTheDocument()
})

it("stays collapsed when auto-expand is turned off", () => {
window.localStorage.setItem("codeg-plan-overlay-auto-expand", "0")
const noPlan = {
id: "live-off",
content: [{ type: "text", text: "working" }],
} as unknown as LiveMessage
const withPlan = {
id: "live-off",
content: [
{ type: "text", text: "working" },
{
type: "plan",
entries: [
{ content: "Build step", priority: "high", status: "in_progress" },
],
},
],
} as unknown as LiveMessage

const { rerender } = renderWithIntl(
<AgentPlanOverlay message={noPlan} isStreaming />
)
rerender(
<NextIntlClientProvider locale="en" messages={enMessages}>
<AgentPlanOverlay message={withPlan} isStreaming />
</NextIntlClientProvider>
)
expect(screen.getByText("Plan 0/1")).toBeInTheDocument()
expect(screen.queryByText("Build step")).not.toBeInTheDocument()
})

it("auto-expands once when a plan is created live while streaming", () => {
// Streaming starts with no plan, then a plan_update lands. The overlay
// should pop open by itself the first time the plan appears.
Expand Down
3 changes: 3 additions & 0 deletions src/components/chat/agent-plan-overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Button } from "@/components/ui/button"
import type { LiveMessage } from "@/contexts/acp-connections-context"
import type { PlanEntryInfo } from "@/lib/types"
import { cn } from "@/lib/utils"
import { usePlanOverlayAutoExpand } from "@/hooks/use-plan-overlay-auto-expand"
import {
CheckCircle2Icon,
ChevronDownIcon,
Expand Down Expand Up @@ -70,6 +71,7 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({
isStreaming = false,
}: AgentPlanOverlayProps) {
const t = useTranslations("Folder.chat.agentPlanOverlay")
const { autoExpand } = usePlanOverlayAutoExpand()
const liveEntries = useMemo(
() => getLatestPlanEntries(message ?? null),
[message]
Expand Down Expand Up @@ -114,6 +116,7 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({
const [autoExpanded, setAutoExpanded] = useState(false)
if (prevLiveHadPlan !== liveHasPlan) {
const planCreatedLive =
autoExpand &&
prevLiveHadPlan === false &&
liveHasPlan &&
isStreaming &&
Expand Down
29 changes: 28 additions & 1 deletion src/components/settings/appearance-settings.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { LayoutGrid, Monitor, Moon, Sun, Type } from "lucide-react"
import { LayoutGrid, ListTodo, Monitor, Moon, Sun, Type } from "lucide-react"
import { useTranslations } from "next-intl"
import { useTheme } from "next-themes"
import { ScrollArea } from "@/components/ui/scroll-area"
Expand Down Expand Up @@ -30,6 +30,7 @@ import { PetManagerSection } from "./pet-manager-section"
import { FontSettingsSection } from "./font-settings-section"
import { WorkspaceBackgroundSection } from "./workspace-background-section"
import { CustomStyleSection } from "./custom-style-section"
import { usePlanOverlayAutoExpand } from "@/hooks/use-plan-overlay-auto-expand"

type ThemeMode = "system" | "light" | "dark"

Expand All @@ -40,6 +41,8 @@ export function AppearanceSettings() {
const { zoomLevel, setZoomLevel } = useZoomLevel()
const { showWelcomeQuickActions, setShowWelcomeQuickActions } =
useWelcomeQuickActions()
const { autoExpand: autoExpandPlan, setAutoExpand: setAutoExpandPlan } =
usePlanOverlayAutoExpand()

const resolvedThemeLabel =
resolvedTheme === "dark"
Expand Down Expand Up @@ -238,6 +241,30 @@ export function AppearanceSettings() {
</label>
</section>

{/* ===== Conversation overlays ===== */}
<section className="rounded-xl border bg-card p-4 space-y-4">
<div className="flex items-center gap-2">
<ListTodo className="h-4 w-4 text-muted-foreground" />
<h2 className="text-sm font-semibold">
{t("planOverlay.sectionTitle")}
</h2>
</div>

<p className="text-xs text-muted-foreground leading-5">
{t("planOverlay.sectionDescription")}
</p>

<label className="flex items-center gap-2">
<Switch
checked={autoExpandPlan}
onCheckedChange={setAutoExpandPlan}
/>
<span className="text-xs text-muted-foreground">
{t("planOverlay.autoExpand")}
</span>
</label>
</section>

{/* ===== Desktop Pet ===== */}
<PetManagerSection />
</div>
Expand Down
39 changes: 39 additions & 0 deletions src/hooks/use-plan-overlay-auto-expand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client"

import { useCallback, useEffect, useState } from "react"

import {
PLAN_OVERLAY_AUTO_EXPAND_EVENT,
STORAGE_KEY_PLAN_OVERLAY_AUTO_EXPAND,
readPlanOverlayAutoExpand,
writePlanOverlayAutoExpand,
} from "@/lib/plan-overlay-prefs"

export function usePlanOverlayAutoExpand(): {
autoExpand: boolean
setAutoExpand: (on: boolean) => void
} {
const [autoExpand, setAutoExpandState] = useState(readPlanOverlayAutoExpand)

useEffect(() => {
const sync = () => setAutoExpandState(readPlanOverlayAutoExpand())
const onStorage = (event: StorageEvent) => {
if (event.key && event.key !== STORAGE_KEY_PLAN_OVERLAY_AUTO_EXPAND)
return
sync()
}
window.addEventListener("storage", onStorage)
window.addEventListener(PLAN_OVERLAY_AUTO_EXPAND_EVENT, sync)
return () => {
window.removeEventListener("storage", onStorage)
window.removeEventListener(PLAN_OVERLAY_AUTO_EXPAND_EVENT, sync)
}
}, [])

const setAutoExpand = useCallback((on: boolean) => {
setAutoExpandState(on)
writePlanOverlayAutoExpand(on)
}, [])

return { autoExpand, setAutoExpand }
}
5 changes: 5 additions & 0 deletions src/i18n/messages/ar.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "بطاقات الاختصار «تطوير الكود / الأعمال المكتبية» التي تظهر أعلى مربع الإدخال في صفحة المحادثة الجديدة.",
"showQuickActions": "العرض في صفحة المحادثة الجديدة"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "خلفية مساحة العمل",
"sectionDescription": "عرض صورة خلف مساحة العمل بالكامل. يصبح الشريط الجانبي واللوحات شبه شفافة ومصنفرة لتظهر الصورة من خلالها، ويحافظ القناع على وضوح النص.",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "Die Shortcut-Karten „Code-Entwicklung / Büroarbeit“ über dem Eingabefeld auf der Seite für eine neue Konversation.",
"showQuickActions": "Auf der Seite für neue Konversationen anzeigen"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "Arbeitsbereich-Hintergrund",
"sectionDescription": "Zeigt ein Bild hinter dem gesamten Arbeitsbereich. Seitenleiste und Panels werden durchscheinend und mattiert, sodass das Bild durchscheint, und eine Maske sorgt für lesbaren Text.",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "The Code Development / Office Work shortcut cards shown above the composer on the new conversation page.",
"showQuickActions": "Show on the new conversation page"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "Workspace background",
"sectionDescription": "Show a picture behind the whole workspace. Sidebar and panels turn translucent and frosted so the image shows through, and a mask keeps text readable.",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "Las tarjetas de acceso rápido «Desarrollo de código / Ofimática» que se muestran sobre el editor en la página de nueva conversación.",
"showQuickActions": "Mostrar en la página de nueva conversación"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "Fondo del espacio de trabajo",
"sectionDescription": "Muestra una imagen detrás de todo el espacio de trabajo. La barra lateral y los paneles se vuelven translúcidos y esmerilados para dejar ver la imagen, y una máscara mantiene el texto legible.",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "Les cartes de raccourci « Développement / Bureautique » affichées au-dessus du champ de saisie sur la page de nouvelle conversation.",
"showQuickActions": "Afficher sur la page de nouvelle conversation"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "Arrière-plan de l'espace de travail",
"sectionDescription": "Affiche une image derrière tout l'espace de travail. La barre latérale et les panneaux deviennent translucides et dépolis pour laisser transparaître l'image, et un masque préserve la lisibilité du texte.",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "新しい会話ページで入力欄の上に表示される「コード開発 / 日常業務」のショートカットカードです。",
"showQuickActions": "新しい会話ページに表示する"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "ワークスペースの背景",
"sectionDescription": "ワークスペース全体の背後に画像を表示します。サイドバーやパネルは半透明のすりガラスになり画像が透けて見えます。マスクで文字の可読性を保ちます。",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "새 대화 페이지에서 입력창 위에 표시되는 '코드 개발 / 일상 업무' 바로가기 카드입니다.",
"showQuickActions": "새 대화 페이지에 표시"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "작업 공간 배경",
"sectionDescription": "작업 공간 전체 뒤에 이미지를 표시합니다. 사이드바와 패널이 반투명 유리 효과로 바뀌어 이미지가 비쳐 보이며, 마스크가 텍스트 가독성을 유지합니다.",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/pt.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "Os cartões de atalho «Desenvolvimento / Escritório» exibidos acima do campo de entrada na página de nova conversa.",
"showQuickActions": "Mostrar na página de nova conversa"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "Plano de fundo da área de trabalho",
"sectionDescription": "Mostra uma imagem atrás de toda a área de trabalho. A barra lateral e os painéis ficam translúcidos e foscos para deixar a imagem transparecer, e uma máscara mantém o texto legível.",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "新会话页面输入框上方显示的「代码开发 / 日常办公」快捷卡片区域。",
"showQuickActions": "在新会话页面显示"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "工作区背景",
"sectionDescription": "在整个工作区背后显示一张图片。侧栏与面板会变半透明并磨砂让图片透出,遮罩保证文字可读。",
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/messages/zh-TW.json
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@
"sectionDescription": "新會話頁面輸入框上方顯示的「程式開發 / 日常辦公」快捷卡片區域。",
"showQuickActions": "在新會話頁面顯示"
},
"planOverlay": {
"sectionTitle": "Agent plan",
"sectionDescription": "The floating plan card that appears on the left of a conversation when an agent writes a to-do list. You can always open it from the small chip.",
"autoExpand": "Open the plan card when an agent starts a plan"
},
"workspaceBackground": {
"sectionTitle": "工作區背景",
"sectionDescription": "在整個工作區背後顯示一張圖片。側邊欄與面板會變半透明並磨砂讓圖片透出,遮罩確保文字可讀。",
Expand Down
26 changes: 26 additions & 0 deletions src/lib/plan-overlay-prefs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { afterEach, describe, expect, it } from "vitest"

import {
DEFAULT_PLAN_OVERLAY_AUTO_EXPAND,
STORAGE_KEY_PLAN_OVERLAY_AUTO_EXPAND,
readPlanOverlayAutoExpand,
writePlanOverlayAutoExpand,
} from "./plan-overlay-prefs"

describe("plan overlay auto-expand preference", () => {
afterEach(() => {
window.localStorage.removeItem(STORAGE_KEY_PLAN_OVERLAY_AUTO_EXPAND)
})

it("defaults to auto-expand (historical behavior)", () => {
expect(readPlanOverlayAutoExpand()).toBe(DEFAULT_PLAN_OVERLAY_AUTO_EXPAND)
expect(readPlanOverlayAutoExpand()).toBe(true)
})

it("round-trips off and on", () => {
writePlanOverlayAutoExpand(false)
expect(readPlanOverlayAutoExpand()).toBe(false)
writePlanOverlayAutoExpand(true)
expect(readPlanOverlayAutoExpand()).toBe(true)
})
})
39 changes: 39 additions & 0 deletions src/lib/plan-overlay-prefs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* Per-device preference for the floating Agent Plan overlay.
*
* When a plan is created mid-turn, the overlay auto-expands into the full
* card. That is useful the first few times and noisy once you already know
* the chip is there. Default stays the historical auto-expand.
*/

export const STORAGE_KEY_PLAN_OVERLAY_AUTO_EXPAND =
"codeg-plan-overlay-auto-expand"
export const PLAN_OVERLAY_AUTO_EXPAND_EVENT = "codeg:plan-overlay-auto-expand"
export const DEFAULT_PLAN_OVERLAY_AUTO_EXPAND = true

export function readPlanOverlayAutoExpand(): boolean {
if (typeof window === "undefined") return DEFAULT_PLAN_OVERLAY_AUTO_EXPAND
try {
const raw = window.localStorage.getItem(
STORAGE_KEY_PLAN_OVERLAY_AUTO_EXPAND
)
if (raw === "0") return false
if (raw === "1") return true
return DEFAULT_PLAN_OVERLAY_AUTO_EXPAND
} catch {
return DEFAULT_PLAN_OVERLAY_AUTO_EXPAND
}
}

export function writePlanOverlayAutoExpand(on: boolean): void {
if (typeof window === "undefined") return
try {
window.localStorage.setItem(
STORAGE_KEY_PLAN_OVERLAY_AUTO_EXPAND,
on ? "1" : "0"
)
window.dispatchEvent(new Event(PLAN_OVERLAY_AUTO_EXPAND_EVENT))
} catch {
// Privacy mode / locked storage: the in-memory hook still updates.
}
}
Loading