Skip to content

Commit 09e71d2

Browse files
committed
feat(ui): polish welcome dialog, window sizing, and cowork UI details
Enlarge default window size and center it on screen, redesign WelcomeDialog buttons and drop the close action, vertically center the cowork welcome view with flexible spacers, and refine artifact tab close buttons and selected-text snippet badge styling.
1 parent 8d2ebb4 commit 09e71d2

7 files changed

Lines changed: 78 additions & 89 deletions

File tree

src/main/windowState.test.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@ import {
99
resolveInitialAppWindowState,
1010
} from './windowState';
1111

12-
test('resolveInitialAppWindowState uses the image-like default size on large displays', () => {
12+
test('resolveInitialAppWindowState uses the centered default size on large displays', () => {
1313
const state = resolveInitialAppWindowState(undefined, [
1414
{ x: 0, y: 0, width: 2560, height: 1440 },
1515
]);
1616

1717
expect(state).toEqual({
18-
x: 768,
19-
y: 380,
18+
x: 640,
19+
y: 320,
2020
width: DEFAULT_APP_WINDOW_WIDTH,
2121
height: DEFAULT_APP_WINDOW_HEIGHT,
2222
isMaximized: false,
@@ -28,10 +28,10 @@ test('resolveInitialAppWindowState scales the default size to fit smaller displa
2828
{ x: 0, y: 0, width: 1000, height: 650 },
2929
]);
3030

31-
expect(state.width).toBe(907);
32-
expect(state.height).toBe(602);
33-
expect(state.x).toBe(47);
34-
expect(state.y).toBe(24);
31+
expect(state.width).toBe(952);
32+
expect(state.height).toBe(600);
33+
expect(state.x).toBe(24);
34+
expect(state.y).toBe(25);
3535
});
3636

3737
test('resolveInitialAppWindowState restores stored bounds on their matching display', () => {

src/main/windowState.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ export const AppWindowStoreKey = {
22
State: 'app_window_state',
33
} as const;
44

5-
export const DEFAULT_APP_WINDOW_WIDTH = 1024;
6-
export const DEFAULT_APP_WINDOW_HEIGHT = 680;
5+
export const DEFAULT_APP_WINDOW_WIDTH = 1280;
6+
export const DEFAULT_APP_WINDOW_HEIGHT = 800;
77
export const MIN_APP_WINDOW_WIDTH = 800;
88
export const MIN_APP_WINDOW_HEIGHT = 600;
99

src/renderer/App.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,6 @@ const App: React.FC = () => {
568568
window.electron.window.close();
569569
}, []);
570570

571-
const handleWelcomeClose = useCallback(() => setShowWelcome(false), []);
572571
const handleWelcomeLogin = useCallback(async () => {
573572
setShowWelcome(false);
574573
await authService.login();
@@ -1116,7 +1115,6 @@ const App: React.FC = () => {
11161115
<WelcomeDialog
11171116
onLogin={handleWelcomeLogin}
11181117
onCustomModel={handleWelcomeCustomModel}
1119-
onClose={handleWelcomeClose}
11201118
/>
11211119
)}
11221120
</div>

src/renderer/components/WelcomeDialog.tsx

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React from 'react';
2+
23
import { i18nService } from '@/services/i18n';
34

45
interface WelcomeDialogProps {
56
onLogin: () => void;
67
onCustomModel: () => void;
7-
onClose: () => void;
88
}
99

10-
const WelcomeDialog: React.FC<WelcomeDialogProps> = ({ onLogin, onCustomModel, onClose }) => {
10+
const WelcomeDialog: React.FC<WelcomeDialogProps> = ({ onLogin, onCustomModel }) => {
1111
return (
1212
<div className="fixed inset-0 z-[60] bg-surface flex items-center justify-center">
1313
{/* gradient overlay */}
@@ -16,17 +16,6 @@ const WelcomeDialog: React.FC<WelcomeDialogProps> = ({ onLogin, onCustomModel, o
1616
style={{ background: 'linear-gradient(360deg, rgba(255, 0, 77, 0) 5.5%, rgba(255, 0, 77, 0.05) 100%)' }}
1717
/>
1818

19-
{/* close button */}
20-
<button
21-
onClick={onClose}
22-
className="absolute top-4 right-4 w-8 h-8 flex items-center justify-center rounded-full text-secondary hover:text-foreground hover:bg-surface-raised transition-colors z-10"
23-
aria-label="Close"
24-
>
25-
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
26-
<path d="M1 1L13 13M13 1L1 13" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" />
27-
</svg>
28-
</button>
29-
3019
{/* content */}
3120
<div className="relative z-10 flex flex-col items-center py-12 w-[420px]">
3221
{/* logo */}
@@ -49,43 +38,47 @@ const WelcomeDialog: React.FC<WelcomeDialogProps> = ({ onLogin, onCustomModel, o
4938
{i18nService.t('welcomeSubtitle')}
5039
</p>
5140

52-
{/* promo badge — left-aligned to match buttons */}
53-
<div className="flex items-center gap-1.5 w-full" style={{ paddingLeft: 11, marginBottom: 10 }}>
54-
<img
55-
src="love.png"
56-
alt=""
57-
width={16}
58-
height={16}
59-
className="select-none shrink-0"
60-
draggable={false}
61-
aria-hidden="true"
62-
/>
63-
<span className="text-sm text-secondary">{i18nService.t('welcomePromo')}</span>
64-
</div>
41+
{/* action stack — login is the primary path, custom model stays visible but quiet */}
42+
<div className="flex flex-col w-[320px]">
43+
{/* promo badge — anchored above the login button as its incentive */}
44+
<div className="flex items-center gap-1.5" style={{ paddingLeft: 11, marginBottom: 10 }}>
45+
<img
46+
src="love.png"
47+
alt=""
48+
width={16}
49+
height={16}
50+
className="select-none shrink-0"
51+
draggable={false}
52+
aria-hidden="true"
53+
/>
54+
<span className="text-sm text-secondary">{i18nService.t('welcomePromo')}</span>
55+
</div>
6556

66-
{/* buttons — hand image sits at bottom-left of this row, overlapping login button */}
67-
<div className="flex gap-3 w-full relative overflow-visible">
68-
<img
69-
src="hand.png"
70-
alt=""
71-
width={41}
72-
height={55}
73-
className="absolute select-none pointer-events-none z-10"
74-
style={{ bottom: 0, left: -8 }}
75-
draggable={false}
76-
aria-hidden="true"
77-
/>
78-
<button
79-
onClick={onLogin}
80-
className="w-[204px] h-10 rounded-xl text-sm font-medium text-white transition-opacity hover:opacity-90 active:opacity-80"
81-
style={{ backgroundColor: 'rgba(72, 133, 255, 1)' }}
82-
>
83-
{i18nService.t('welcomeLogin')}
84-
</button>
57+
{/* primary: login — hand image overlaps its bottom-left corner */}
58+
<div className="relative w-full overflow-visible">
59+
<img
60+
src="hand.png"
61+
alt=""
62+
width={41}
63+
height={55}
64+
className="absolute select-none pointer-events-none z-10"
65+
style={{ bottom: 0, left: -8 }}
66+
draggable={false}
67+
aria-hidden="true"
68+
/>
69+
<button
70+
onClick={onLogin}
71+
className="w-full h-11 rounded-xl text-sm font-medium text-white transition-opacity hover:opacity-90 active:opacity-80 shadow-[0_4px_14px_rgba(72,133,255,0.35)]"
72+
style={{ backgroundColor: 'rgba(72, 133, 255, 1)' }}
73+
>
74+
{i18nService.t('welcomeLogin')}
75+
</button>
76+
</div>
77+
78+
{/* secondary: custom model — ghost style keeps it discoverable without competing */}
8579
<button
8680
onClick={onCustomModel}
87-
className="w-[204px] h-10 rounded-xl text-sm font-medium text-white transition-opacity hover:opacity-90 active:opacity-80"
88-
style={{ backgroundColor: 'rgba(54, 57, 63, 1)' }}
81+
className="mt-3 w-full h-10 rounded-xl text-sm font-medium text-secondary border border-border bg-transparent hover:text-foreground hover:bg-surface-raised transition-colors"
8982
>
9083
{i18nService.t('welcomeCustomModel')}
9184
</button>

src/renderer/components/cowork/CoworkSessionDetail.tsx

Lines changed: 11 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -931,6 +931,9 @@ const ArtifactTabPlusIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) =>
931931
</svg>
932932
);
933933

934+
const artifactTabCloseButtonClassName =
935+
'mr-1 flex h-3.5 w-3.5 shrink-0 items-center justify-center rounded-full text-transparent transition-colors group-hover:bg-muted group-hover:text-background hover:!bg-foreground hover:!text-background';
936+
934937
const ArtifactBrowserTabIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
935938
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.25" strokeLinecap="round" strokeLinejoin="round" {...props}>
936939
<circle cx="8" cy="8" r="6" />
@@ -4392,14 +4395,10 @@ const CoworkSessionDetail: React.FC<CoworkSessionDetailProps> = ({
43924395
event.stopPropagation();
43934396
handleCloseArtifactFileListTab();
43944397
}}
4395-
className={`mr-1 rounded p-0.5 transition-colors ${
4396-
activeArtifactPreviewTab || activeSpecialPreviewTab !== ArtifactSpecialTab.FileList
4397-
? 'text-transparent group-hover:text-secondary group-hover:hover:bg-surface-hover group-hover:hover:text-foreground'
4398-
: 'text-secondary hover:bg-surface-hover hover:text-foreground'
4399-
}`}
4398+
className={artifactTabCloseButtonClassName}
44004399
title={i18nService.t('artifactCloseTab')}
44014400
>
4402-
<ArtifactTabCloseIcon className="h-3 w-3" />
4401+
<ArtifactTabCloseIcon className="h-2.5 w-2.5" />
44034402
</button>
44044403
</div>
44054404
)}
@@ -4431,14 +4430,10 @@ const CoworkSessionDetail: React.FC<CoworkSessionDetailProps> = ({
44314430
event.stopPropagation();
44324431
handleCloseArtifactBrowserTab();
44334432
}}
4434-
className={`mr-1 rounded p-0.5 transition-colors ${
4435-
activeArtifactPreviewTab || activeSpecialPreviewTab !== ArtifactSpecialTab.Browser
4436-
? 'text-transparent group-hover:text-secondary group-hover:hover:bg-surface-hover group-hover:hover:text-foreground'
4437-
: 'text-secondary hover:bg-surface-hover hover:text-foreground'
4438-
}`}
4433+
className={artifactTabCloseButtonClassName}
44394434
title={i18nService.t('artifactCloseTab')}
44404435
>
4441-
<ArtifactTabCloseIcon className="h-3 w-3" />
4436+
<ArtifactTabCloseIcon className="h-2.5 w-2.5" />
44424437
</button>
44434438
</div>
44444439
)}
@@ -4470,14 +4465,10 @@ const CoworkSessionDetail: React.FC<CoworkSessionDetailProps> = ({
44704465
event.stopPropagation();
44714466
handleCloseArtifactSubagentTab();
44724467
}}
4473-
className={`mr-1 rounded p-0.5 transition-colors ${
4474-
activeArtifactPreviewTab || activeSpecialPreviewTab !== ArtifactSpecialTab.Subagents
4475-
? 'text-transparent group-hover:text-secondary group-hover:hover:bg-surface-hover group-hover:hover:text-foreground'
4476-
: 'text-secondary hover:bg-surface-hover hover:text-foreground'
4477-
}`}
4468+
className={artifactTabCloseButtonClassName}
44784469
title={i18nService.t('artifactCloseTab')}
44794470
>
4480-
<ArtifactTabCloseIcon className="h-3 w-3" />
4471+
<ArtifactTabCloseIcon className="h-2.5 w-2.5" />
44814472
</button>
44824473
</div>
44834474
)}
@@ -4509,14 +4500,10 @@ const CoworkSessionDetail: React.FC<CoworkSessionDetailProps> = ({
45094500
event.stopPropagation();
45104501
handleCloseArtifactTab(tab.id);
45114502
}}
4512-
className={`mr-1 rounded p-0.5 transition-colors ${
4513-
isActive
4514-
? 'text-secondary hover:bg-surface-hover hover:text-foreground'
4515-
: 'text-transparent group-hover:text-secondary group-hover:hover:bg-surface-hover group-hover:hover:text-foreground'
4516-
}`}
4503+
className={artifactTabCloseButtonClassName}
45174504
title={i18nService.t('artifactCloseTab')}
45184505
>
4519-
<ArtifactTabCloseIcon className="h-3 w-3" />
4506+
<ArtifactTabCloseIcon className="h-2.5 w-2.5" />
45204507
</button>
45214508
</div>
45224509
);

src/renderer/components/cowork/CoworkView.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,7 +779,11 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
779779

780780
{/* Main Content */}
781781
<div className="flex-1 overflow-y-auto min-h-0 relative">
782-
<div className="relative flex min-h-full w-full min-w-[320px] flex-col items-center px-4 pt-[clamp(88px,19vh,140px)] pb-8">
782+
<div className="relative flex min-h-full w-full min-w-[320px] flex-col items-center px-4 py-8">
783+
{/* Flexible spacers (2:3) keep the welcome block at the optical
784+
center on tall windows; min-h preserves breathing room before
785+
the page starts scrolling on short windows. */}
786+
<div aria-hidden="true" className="w-full min-h-[56px] flex-[2_0_0px]" />
783787
{/* Welcome Section - staggered entrance animation */}
784788
<div className="w-full max-w-3xl text-center">
785789
<img
@@ -842,6 +846,7 @@ const CoworkView: React.FC<CoworkViewProps> = ({ onRequestAppSettings, onShowSki
842846
)}
843847
<CreditsResetCampaignFloat />
844848
</div>
849+
<div aria-hidden="true" className="w-full min-h-[24px] flex-[3_0_0px]" />
845850
</div>
846851
</div>
847852
</div>

src/renderer/components/cowork/SelectedTextSnippetBadge.tsx

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,10 @@ const SelectedTextSnippetBadge: React.FC<SelectedTextSnippetBadgeProps> = ({
7070
if (snippets.length === 0) return null;
7171
const canRemove = Boolean(onClear || onRemove);
7272
const popoverAlignmentClass = align === 'right' ? 'right-0' : 'left-0';
73+
const popoverOriginClass = align === 'right' ? 'origin-bottom-right' : 'origin-bottom-left';
74+
const pillSurfaceClass = expanded
75+
? 'bg-surface-raised/70'
76+
: 'bg-surface hover:bg-surface-raised/50';
7377
const popoverWidthUnits = clampSelectedTextPopoverWidth(
7478
snippets.reduce((longestWidth, snippet) => {
7579
const sourceLabelWidth = getApproximateTextWidthUnits(getSourceLabel(snippet));
@@ -92,13 +96,14 @@ const SelectedTextSnippetBadge: React.FC<SelectedTextSnippetBadgeProps> = ({
9296

9397
return (
9498
<div ref={rootRef} className="group relative inline-flex max-w-full">
95-
<div className="inline-flex h-7 max-w-full items-center rounded-full border border-border bg-surface-raised text-xs text-foreground shadow-subtle transition-colors hover:bg-surface">
99+
<div className={`inline-flex h-7 max-w-full items-center rounded-full border border-border text-xs text-foreground shadow-subtle transition-colors ${pillSurfaceClass}`}>
96100
<button
97101
type="button"
98102
onClick={() => setExpanded(value => !value)}
103+
aria-expanded={expanded}
99104
className="inline-flex h-full min-w-0 items-center gap-1.5 rounded-full pl-2.5 pr-2 text-left"
100105
>
101-
<SelectedTextIcon className="h-4 w-4 shrink-0 text-foreground" />
106+
<SelectedTextIcon className="h-4 w-4 shrink-0 text-secondary" />
102107
<span className="min-w-0 truncate">
103108
{i18nService.t('coworkSelectedTextSnippetCount').replace('{count}', String(snippets.length))}
104109
</span>
@@ -107,7 +112,7 @@ const SelectedTextSnippetBadge: React.FC<SelectedTextSnippetBadgeProps> = ({
107112
<button
108113
type="button"
109114
onClick={handleClear}
110-
className="pointer-events-none mr-1 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-surface text-secondary opacity-0 transition-all hover:bg-surface-hover hover:text-foreground group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100"
115+
className="pointer-events-none mr-1 inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-muted opacity-0 transition-all hover:bg-surface-raised hover:text-foreground group-hover:pointer-events-auto group-hover:opacity-100 group-focus-within:pointer-events-auto group-focus-within:opacity-100"
111116
title={i18nService.t('coworkSelectedTextRemove')}
112117
aria-label={i18nService.t('coworkSelectedTextRemove')}
113118
>
@@ -117,12 +122,13 @@ const SelectedTextSnippetBadge: React.FC<SelectedTextSnippetBadgeProps> = ({
117122
</div>
118123
{expanded && (
119124
<div
120-
className={`absolute bottom-full ${popoverAlignmentClass} z-50 mb-1.5 max-w-[calc(100vw-48px)] rounded-xl border border-border bg-surface p-2 shadow-popover`}
125+
className={`absolute bottom-full ${popoverAlignmentClass} z-50 mb-1.5 max-w-[calc(100vw-48px)] ${popoverOriginClass} animate-scale-in rounded-xl border border-border bg-surface p-1.5 shadow-popover`}
121126
style={popoverStyle}
122127
>
123128
<div className="flex max-h-56 max-w-full flex-col items-stretch gap-1 overflow-y-auto">
124129
{snippets.map(snippet => (
125-
<div key={snippet.id} className="flex w-full items-start gap-1 rounded-lg bg-surface-raised px-2 py-1.5 text-xs text-secondary">
130+
<div key={snippet.id} className="group/snippet relative flex w-full items-start gap-1 rounded-lg bg-surface-raised/40 py-1.5 pl-3.5 pr-1.5 text-xs text-secondary transition-colors hover:bg-surface-raised/80">
131+
<span aria-hidden="true" className="absolute inset-y-1.5 left-1.5 w-0.5 rounded-full bg-primary/40 transition-colors group-hover/snippet:bg-primary/60" />
126132
<button
127133
type="button"
128134
onClick={() => {
@@ -145,7 +151,7 @@ const SelectedTextSnippetBadge: React.FC<SelectedTextSnippetBadgeProps> = ({
145151
<button
146152
type="button"
147153
onClick={() => onRemove(snippet.id)}
148-
className="shrink-0 rounded p-0.5 hover:bg-surface"
154+
className="pointer-events-none shrink-0 rounded-md p-0.5 text-muted opacity-0 transition-all hover:bg-surface hover:text-foreground focus-visible:opacity-100 group-hover/snippet:pointer-events-auto group-hover/snippet:opacity-100 group-focus-within/snippet:pointer-events-auto group-focus-within/snippet:opacity-100"
149155
title={i18nService.t('coworkSelectedTextRemove')}
150156
>
151157
<XMarkIcon className="h-3.5 w-3.5" />

0 commit comments

Comments
 (0)