feat: blue theme migration and chat input UI polish
- Migrate purple/violet theme to blue across entire codebase (globals.css, ToolCallBlock, ChatListPanel, SkillEditor, SkillListItem, e2e tests) - Increase input box border radius (rounded-md → rounded-2xl) and shadow - Add arrow flip animation on mode/model selector dropdowns - Change send button to circular with up-arrow icon - Increase global Hugeicons stroke weight (1.5 → 2) - Remove theme-colored focus ring on input box Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
1 parent
ee65c62d62
commit
9635585ab6
11 files changed
+54
-35
No files matched your search
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codepilot",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codepilot",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"dependencies": {
|
||||
"@anthropic-ai/claude-agent-sdk": "^0.2.33",
|
||||
"@hugeicons/core-free-icons": "^3.1.1",
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codepilot",
|
||||
"version": "0.6.1",
|
||||
"version": "0.6.2",
|
||||
"private": true,
|
||||
"main": "dist-electron/main.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -30,7 +30,7 @@ test.describe('Chat UI Enhanced (V2)', () => {
|
||||
// Wait for assistant response -- V2 uses gradient avatar instead of text label
|
||||
// Detect assistant response via the gradient avatar
|
||||
await expect(
|
||||
page.locator('.from-violet-500')
|
||||
page.locator('[data-role="assistant"]')
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
// Wait for streaming to complete
|
||||
@@ -50,7 +50,7 @@ test.describe('Chat UI Enhanced (V2)', () => {
|
||||
await sendMessage(page, 'Show me a Python hello world');
|
||||
|
||||
await expect(
|
||||
page.locator('.from-violet-500')
|
||||
page.locator('[data-role="assistant"]')
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
|
||||
await waitForStreamingEnd(page);
|
||||
@@ -103,9 +103,9 @@ test.describe('Chat UI Enhanced (V2)', () => {
|
||||
await goToChat(page);
|
||||
await sendMessage(page, 'Hi there');
|
||||
|
||||
// V2: Assistant avatar uses gradient bg (from-violet-500 to-purple-600)
|
||||
// Detect assistant response via data-role attribute
|
||||
await expect(
|
||||
page.locator('.from-violet-500')
|
||||
page.locator('[data-role="assistant"]')
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
|
||||
@@ -125,7 +125,7 @@ test.describe('Chat UI Enhanced (V2)', () => {
|
||||
|
||||
// V2: Assistant avatar is gradient, not bg-primary
|
||||
await expect(
|
||||
page.locator('.from-violet-500:has(svg)')
|
||||
page.locator('[data-role="assistant"]')
|
||||
).toBeVisible({ timeout: 30_000 });
|
||||
});
|
||||
});
|
||||
|
||||
@@ -116,9 +116,9 @@ test.describe('Chat Page', () => {
|
||||
await goToChat(page);
|
||||
await sendMessage(page, 'Say hello');
|
||||
|
||||
// V2: Wait for assistant response -- gradient avatar appears
|
||||
// Wait for assistant response
|
||||
await expect(
|
||||
page.locator('.from-violet-500')
|
||||
page.locator('[data-role="assistant"]')
|
||||
).toBeVisible({ timeout: 10_000 });
|
||||
});
|
||||
|
||||
|
||||
+25
-15
@@ -54,7 +54,7 @@
|
||||
--card-foreground: oklch(0.147 0.004 49.25);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.147 0.004 49.25);
|
||||
--primary: oklch(0.457 0.24 277.023);
|
||||
--primary: oklch(0.546 0.245 262.881);
|
||||
--primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--secondary: oklch(0.97 0.001 106.424);
|
||||
--secondary-foreground: oklch(0.216 0.006 56.043);
|
||||
@@ -65,20 +65,20 @@
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.923 0.003 48.717);
|
||||
--input: oklch(0.923 0.003 48.717);
|
||||
--ring: oklch(0.457 0.24 277.023);
|
||||
--chart-1: oklch(0.457 0.24 277.023);
|
||||
--ring: oklch(0.546 0.245 262.881);
|
||||
--chart-1: oklch(0.546 0.245 262.881);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0.001 106.423);
|
||||
--sidebar-foreground: oklch(0.147 0.004 49.25);
|
||||
--sidebar-primary: oklch(0.457 0.24 277.023);
|
||||
--sidebar-primary: oklch(0.546 0.245 262.881);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-accent: oklch(0.97 0.001 106.424);
|
||||
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
|
||||
--sidebar-border: oklch(0.923 0.003 48.717);
|
||||
--sidebar-ring: oklch(0.457 0.24 277.023);
|
||||
--sidebar-ring: oklch(0.546 0.245 262.881);
|
||||
}
|
||||
|
||||
.dark {
|
||||
@@ -88,7 +88,7 @@
|
||||
--card-foreground: oklch(0.985 0.001 106.423);
|
||||
--popover: oklch(0.216 0.006 56.043);
|
||||
--popover-foreground: oklch(0.985 0.001 106.423);
|
||||
--primary: oklch(0.585 0.233 277.117);
|
||||
--primary: oklch(0.623 0.214 259.815);
|
||||
--primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--secondary: oklch(0.268 0.007 34.298);
|
||||
--secondary-foreground: oklch(0.985 0.001 106.423);
|
||||
@@ -99,20 +99,20 @@
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.585 0.233 277.117);
|
||||
--chart-1: oklch(0.585 0.233 277.117);
|
||||
--ring: oklch(0.623 0.214 259.815);
|
||||
--chart-1: oklch(0.623 0.214 259.815);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.216 0.006 56.043);
|
||||
--sidebar-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-primary: oklch(0.585 0.233 277.117);
|
||||
--sidebar-primary: oklch(0.623 0.214 259.815);
|
||||
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-accent: oklch(0.268 0.007 34.298);
|
||||
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.585 0.233 277.117);
|
||||
--sidebar-ring: oklch(0.623 0.214 259.815);
|
||||
}
|
||||
|
||||
@layer base {
|
||||
@@ -125,10 +125,10 @@
|
||||
|
||||
/* Selection color */
|
||||
::selection {
|
||||
background: oklch(0.7 0.15 280 / 30%);
|
||||
background: oklch(0.7 0.15 260 / 30%);
|
||||
}
|
||||
.dark ::selection {
|
||||
background: oklch(0.6 0.2 280 / 35%);
|
||||
background: oklch(0.6 0.2 260 / 35%);
|
||||
}
|
||||
|
||||
/* Custom scrollbar */
|
||||
@@ -179,16 +179,16 @@
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
.prose blockquote {
|
||||
border-left-color: oklch(0.7 0.15 280 / 40%);
|
||||
border-left-color: oklch(0.7 0.15 260 / 40%);
|
||||
font-style: normal;
|
||||
}
|
||||
.prose a {
|
||||
color: oklch(0.55 0.2 260);
|
||||
color: oklch(0.55 0.2 250);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.dark .prose a {
|
||||
color: oklch(0.7 0.18 260);
|
||||
color: oklch(0.7 0.18 250);
|
||||
}
|
||||
.prose hr {
|
||||
border-color: var(--border);
|
||||
@@ -197,3 +197,13 @@
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
|
||||
/* Global icon stroke weight — make Hugeicons strokes slightly thicker */
|
||||
svg.hugeicons,
|
||||
svg.hugeicons path,
|
||||
svg.hugeicons line,
|
||||
svg.hugeicons circle,
|
||||
svg.hugeicons polyline,
|
||||
svg.hugeicons rect {
|
||||
stroke-width: 2;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
ClipboardIcon,
|
||||
HelpCircleIcon,
|
||||
ArrowDown01Icon,
|
||||
ArrowUp02Icon,
|
||||
CommandLineIcon,
|
||||
Attachment01Icon,
|
||||
Cancel01Icon,
|
||||
@@ -32,6 +33,7 @@ import {
|
||||
PromptInputSubmit,
|
||||
usePromptInputAttachments,
|
||||
} from '@/components/ai-elements/prompt-input';
|
||||
import { SquareIcon } from 'lucide-react';
|
||||
import type { ChatStatus } from 'ai';
|
||||
import type { FileAttachment } from '@/types';
|
||||
import { nanoid } from 'nanoid';
|
||||
@@ -221,7 +223,14 @@ function FileAwareSubmitButton({
|
||||
status={status}
|
||||
onStop={onStop}
|
||||
disabled={disabled || (!isStreaming && !inputValue.trim() && !hasBadge && !hasFiles)}
|
||||
/>
|
||||
className="rounded-full"
|
||||
>
|
||||
{isStreaming ? (
|
||||
<SquareIcon className="size-4" />
|
||||
) : (
|
||||
<HugeiconsIcon icon={ArrowUp02Icon} className="h-4 w-4" strokeWidth={2} />
|
||||
)}
|
||||
</PromptInputSubmit>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -887,7 +896,7 @@ export function MessageInput({
|
||||
>
|
||||
<HugeiconsIcon icon={currentMode.icon} className="h-3.5 w-3.5" />
|
||||
<span className="text-xs">{currentMode.label}</span>
|
||||
<HugeiconsIcon icon={ArrowDown01Icon} className="h-2.5 w-2.5" />
|
||||
<HugeiconsIcon icon={ArrowDown01Icon} className={cn("h-2.5 w-2.5 transition-transform duration-200", modeMenuOpen && "rotate-180")} />
|
||||
</PromptInputButton>
|
||||
|
||||
{/* Mode dropdown */}
|
||||
@@ -931,7 +940,7 @@ export function MessageInput({
|
||||
onClick={() => setModelMenuOpen((prev) => !prev)}
|
||||
>
|
||||
<span className="text-xs font-mono">{currentModelOption.label}</span>
|
||||
<HugeiconsIcon icon={ArrowDown01Icon} className="h-2.5 w-2.5" />
|
||||
<HugeiconsIcon icon={ArrowDown01Icon} className={cn("h-2.5 w-2.5 transition-transform duration-200", modelMenuOpen && "rotate-180")} />
|
||||
</PromptInputButton>
|
||||
|
||||
{modelMenuOpen && (
|
||||
|
||||
@@ -312,7 +312,7 @@ export function ToolCallBlock({
|
||||
category === 'read' && "text-blue-500",
|
||||
category === 'write' && "text-amber-500",
|
||||
category === 'bash' && "text-green-500",
|
||||
category === 'search' && "text-purple-500",
|
||||
category === 'search' && "text-indigo-500",
|
||||
category === 'other' && "text-zinc-500",
|
||||
)} />
|
||||
<span className="font-mono text-xs truncate flex-1 text-foreground/80">{summary}</span>
|
||||
|
||||
@@ -65,7 +65,7 @@ function groupSessionsByDate(
|
||||
|
||||
const MODE_BADGE_CONFIG = {
|
||||
code: { label: "Code", className: "bg-blue-500/10 text-blue-500" },
|
||||
plan: { label: "Plan", className: "bg-purple-500/10 text-purple-500" },
|
||||
plan: { label: "Plan", className: "bg-sky-500/10 text-sky-500" },
|
||||
ask: { label: "Ask", className: "bg-green-500/10 text-green-500" },
|
||||
} as const;
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ export function SkillEditor({ skill, onSave, onDelete }: SkillEditorProps) {
|
||||
: skill.source === "installed"
|
||||
? "border-orange-500/40 text-orange-600 dark:text-orange-400"
|
||||
: skill.source === "plugin"
|
||||
? "border-purple-500/40 text-purple-600 dark:text-purple-400"
|
||||
? "border-indigo-500/40 text-indigo-600 dark:text-indigo-400"
|
||||
: "border-blue-500/40 text-blue-600 dark:text-blue-400"
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -77,7 +77,7 @@ export function SkillListItem({
|
||||
: skill.source === "installed"
|
||||
? "border-orange-500/40 text-orange-600 dark:text-orange-400"
|
||||
: skill.source === "plugin"
|
||||
? "border-purple-500/40 text-purple-600 dark:text-purple-400"
|
||||
? "border-indigo-500/40 text-indigo-600 dark:text-indigo-400"
|
||||
: "border-blue-500/40 text-blue-600 dark:text-blue-400"
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -14,7 +14,7 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
data-slot="input-group"
|
||||
role="group"
|
||||
className={cn(
|
||||
"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-md border shadow-xs transition-[color,box-shadow] outline-none",
|
||||
"group/input-group border-input dark:bg-input/30 relative flex w-full items-center rounded-2xl border shadow-md transition-[color,box-shadow] outline-none",
|
||||
"h-9 min-w-0 has-[>textarea]:h-auto",
|
||||
|
||||
// Variants based on alignment.
|
||||
@@ -23,8 +23,8 @@ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
|
||||
"has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
|
||||
"has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
|
||||
|
||||
// Focus state.
|
||||
"has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50 has-[[data-slot=input-group-control]:focus-visible]:ring-[3px]",
|
||||
// Focus state — neutral border only, no colored ring.
|
||||
"has-[[data-slot=input-group-control]:focus-visible]:border-border",
|
||||
|
||||
// Error state.
|
||||
"has-[[data-slot][aria-invalid=true]]:ring-destructive/20 has-[[data-slot][aria-invalid=true]]:border-destructive dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
|
||||
|
||||
Reference in new issue
Block a user