mirror of
https://github.com/davidkaya/aryx.git
synced 2026-08-26 04:43:56 +02:00
feat: improve onboarding and first-run experience
Redesigned WelcomePane with context-aware onboarding: - Getting Started progress tracker with animated progress bar showing Copilot connection + project setup completion - Adaptive CTAs: highlights 'Connect GitHub Copilot' when not connected, or 'Try a Quick Scratchpad' when connected - Setup steps with checkmarks for completed items - Keyboard shortcut hints for returning users - First-run vs returning-user messaging Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -592,6 +592,7 @@ export default function App() {
|
|||||||
content = (
|
content = (
|
||||||
<WelcomePane
|
<WelcomePane
|
||||||
hasProjects={hasUserProjects}
|
hasProjects={hasUserProjects}
|
||||||
|
connectionStatus={sidecarCapabilities?.connection.status}
|
||||||
onAddProject={() => void api.addProject()}
|
onAddProject={() => void api.addProject()}
|
||||||
onNewScratchpad={() => handleCreateScratchpad()}
|
onNewScratchpad={() => handleCreateScratchpad()}
|
||||||
onOpenSettings={() => setShowSettings(true)}
|
onOpenSettings={() => setShowSettings(true)}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
import { FolderPlus, MessageSquarePlus, Settings } from 'lucide-react';
|
import { CheckCircle2, Circle, FolderPlus, MessageSquarePlus, Settings, Zap } from 'lucide-react';
|
||||||
import { motion } from 'motion/react';
|
import { motion } from 'motion/react';
|
||||||
|
|
||||||
|
import type { SidecarConnectionStatus } from '@shared/contracts/sidecar';
|
||||||
import appIconUrl from '../../../assets/icons/icon.png';
|
import appIconUrl from '../../../assets/icons/icon.png';
|
||||||
|
|
||||||
interface WelcomePaneProps {
|
interface WelcomePaneProps {
|
||||||
hasProjects: boolean;
|
hasProjects: boolean;
|
||||||
|
connectionStatus?: SidecarConnectionStatus;
|
||||||
onNewScratchpad: () => void;
|
onNewScratchpad: () => void;
|
||||||
onAddProject: () => void;
|
onAddProject: () => void;
|
||||||
onOpenSettings: () => void;
|
onOpenSettings: () => void;
|
||||||
@@ -22,14 +24,19 @@ interface ActionCardProps {
|
|||||||
title: string;
|
title: string;
|
||||||
description: string;
|
description: string;
|
||||||
onClick: () => void;
|
onClick: () => void;
|
||||||
|
highlight?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ActionCard({ icon, title, description, onClick }: ActionCardProps) {
|
function ActionCard({ icon, title, description, onClick, highlight }: ActionCardProps) {
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className="group flex w-full cursor-pointer items-center gap-4 rounded-xl border border-[var(--color-glass-border)] bg-[var(--color-glass)] px-5 py-4 text-left backdrop-blur-sm transition-all duration-200 hover:border-[var(--color-border-glow)] hover:shadow-[0_0_20px_rgba(36,92,249,0.08),0_4px_12px_rgba(0,0,0,0.2)]"
|
className={`group flex w-full cursor-pointer items-center gap-4 rounded-xl border px-5 py-4 text-left backdrop-blur-sm transition-all duration-200 ${
|
||||||
|
highlight
|
||||||
|
? 'border-[var(--color-border-glow)] bg-[var(--color-accent-muted)] shadow-[0_0_24px_rgba(36,92,249,0.1)] hover:shadow-[0_0_32px_rgba(36,92,249,0.15)]'
|
||||||
|
: 'border-[var(--color-glass-border)] bg-[var(--color-glass)] hover:border-[var(--color-border-glow)] hover:shadow-[0_0_20px_rgba(36,92,249,0.08),0_4px_12px_rgba(0,0,0,0.2)]'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="brand-gradient-bg flex size-9 shrink-0 items-center justify-center rounded-full">
|
<div className="brand-gradient-bg flex size-9 shrink-0 items-center justify-center rounded-full">
|
||||||
{icon}
|
{icon}
|
||||||
@@ -46,12 +53,42 @@ function ActionCard({ icon, title, description, onClick }: ActionCardProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface SetupStepProps {
|
||||||
|
label: string;
|
||||||
|
done: boolean;
|
||||||
|
active?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
function SetupStep({ label, done, active }: SetupStepProps) {
|
||||||
|
return (
|
||||||
|
<div className={`flex items-center gap-2 text-[12px] ${active ? 'text-[var(--color-text-primary)]' : 'text-[var(--color-text-muted)]'}`}>
|
||||||
|
{done
|
||||||
|
? <CheckCircle2 className="size-3.5 text-[var(--color-status-success)]" />
|
||||||
|
: <Circle className={`size-3.5 ${active ? 'text-[var(--color-accent)]' : 'text-[var(--color-text-muted)]'}`} />
|
||||||
|
}
|
||||||
|
<span className={done ? 'line-through opacity-60' : ''}>{label}</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function WelcomePane({
|
export function WelcomePane({
|
||||||
hasProjects,
|
hasProjects,
|
||||||
|
connectionStatus,
|
||||||
onNewScratchpad,
|
onNewScratchpad,
|
||||||
onAddProject,
|
onAddProject,
|
||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
}: WelcomePaneProps) {
|
}: WelcomePaneProps) {
|
||||||
|
const isConnected = connectionStatus === 'ready';
|
||||||
|
const isFirstRun = !hasProjects;
|
||||||
|
|
||||||
|
// Determine setup progress
|
||||||
|
const steps = [
|
||||||
|
{ label: 'GitHub Copilot connected', done: isConnected },
|
||||||
|
{ label: 'First project added', done: hasProjects },
|
||||||
|
];
|
||||||
|
const completedSteps = steps.filter((s) => s.done).length;
|
||||||
|
const allDone = completedSteps === steps.length;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="relative flex h-full flex-col items-center justify-center overflow-hidden px-8">
|
<div className="relative flex h-full flex-col items-center justify-center overflow-hidden px-8">
|
||||||
{/* Ambient nebula glow */}
|
{/* Ambient nebula glow */}
|
||||||
@@ -67,7 +104,7 @@ export function WelcomePane({
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="relative z-10 flex w-full max-w-xs flex-col items-center gap-8 text-center">
|
<div className="relative z-10 flex w-full max-w-sm flex-col items-center gap-6 text-center">
|
||||||
{/* Icon */}
|
{/* Icon */}
|
||||||
<motion.div {...fadeUp(0)}>
|
<motion.div {...fadeUp(0)}>
|
||||||
<img
|
<img
|
||||||
@@ -82,22 +119,70 @@ export function WelcomePane({
|
|||||||
{/* Heading */}
|
{/* Heading */}
|
||||||
<motion.div {...fadeUp(0.08)}>
|
<motion.div {...fadeUp(0.08)}>
|
||||||
<h1 className="font-display brand-gradient-text text-2xl font-bold tracking-tight">
|
<h1 className="font-display brand-gradient-text text-2xl font-bold tracking-tight">
|
||||||
aryx
|
{isFirstRun ? 'Welcome to Aryx' : 'aryx'}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="mt-2 max-w-sm text-[13px] leading-relaxed text-[var(--color-text-secondary)]">
|
<p className="mt-2 max-w-sm text-[13px] leading-relaxed text-[var(--color-text-secondary)]">
|
||||||
Start a scratchpad conversation for ad-hoc questions or connect a project to work with
|
{isFirstRun
|
||||||
repo-aware Copilot agents.
|
? 'Your AI workspace powered by GitHub Copilot. Start a scratchpad for quick questions or connect a project for full agent support.'
|
||||||
|
: 'Start a scratchpad conversation for ad-hoc questions or connect a project to work with repo-aware Copilot agents.'
|
||||||
|
}
|
||||||
</p>
|
</p>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Setup progress — only for first-run */}
|
||||||
|
{isFirstRun && !allDone && (
|
||||||
|
<motion.div {...fadeUp(0.12)} className="w-full rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)]/60 p-4">
|
||||||
|
<div className="mb-3 flex items-center justify-between">
|
||||||
|
<span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--color-text-muted)]">
|
||||||
|
Getting started
|
||||||
|
</span>
|
||||||
|
<span className="text-[11px] text-[var(--color-text-muted)]">
|
||||||
|
{completedSteps}/{steps.length}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/* Progress bar */}
|
||||||
|
<div className="mb-3 h-1 overflow-hidden rounded-full bg-[var(--color-surface-3)]">
|
||||||
|
<motion.div
|
||||||
|
className="h-full rounded-full bg-gradient-to-r from-[var(--color-accent)] to-[var(--color-accent-purple)]"
|
||||||
|
initial={{ width: 0 }}
|
||||||
|
animate={{ width: `${(completedSteps / steps.length) * 100}%` }}
|
||||||
|
transition={{ duration: 0.6, ease: 'easeOut', delay: 0.3 }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="space-y-2">
|
||||||
|
{steps.map((step, i) => (
|
||||||
|
<SetupStep
|
||||||
|
key={step.label}
|
||||||
|
label={step.label}
|
||||||
|
done={step.done}
|
||||||
|
active={!step.done && steps.slice(0, i).every((s) => s.done)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Action cards */}
|
{/* Action cards */}
|
||||||
<motion.div {...fadeUp(0.16)} className="flex w-full flex-col gap-2.5">
|
<motion.div {...fadeUp(isFirstRun && !allDone ? 0.2 : 0.16)} className="flex w-full flex-col gap-2.5">
|
||||||
|
{/* Primary CTA adapts to state */}
|
||||||
|
{!isConnected && (
|
||||||
|
<ActionCard
|
||||||
|
icon={<Zap className="size-4 text-white" />}
|
||||||
|
title="Connect GitHub Copilot"
|
||||||
|
description="Check connection status and configure your CLI"
|
||||||
|
onClick={onOpenSettings}
|
||||||
|
highlight
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
<ActionCard
|
<ActionCard
|
||||||
icon={<MessageSquarePlus className="size-4 text-white" />}
|
icon={<MessageSquarePlus className="size-4 text-white" />}
|
||||||
title="New Scratchpad"
|
title={isFirstRun ? 'Try a Quick Scratchpad' : 'New Scratchpad'}
|
||||||
description="Ask anything without a project context"
|
description={isFirstRun ? 'Start a conversation — no setup needed' : 'Ask anything without a project context'}
|
||||||
onClick={onNewScratchpad}
|
onClick={onNewScratchpad}
|
||||||
|
highlight={isConnected && isFirstRun}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{!hasProjects && (
|
{!hasProjects && (
|
||||||
<ActionCard
|
<ActionCard
|
||||||
icon={<FolderPlus className="size-4 text-white" />}
|
icon={<FolderPlus className="size-4 text-white" />}
|
||||||
@@ -106,6 +191,7 @@ export function WelcomePane({
|
|||||||
onClick={onAddProject}
|
onClick={onAddProject}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<ActionCard
|
<ActionCard
|
||||||
icon={<Settings className="size-4 text-white" />}
|
icon={<Settings className="size-4 text-white" />}
|
||||||
title="Manage Patterns"
|
title="Manage Patterns"
|
||||||
@@ -113,6 +199,20 @@ export function WelcomePane({
|
|||||||
onClick={onOpenSettings}
|
onClick={onOpenSettings}
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
{/* Keyboard shortcut hints for returning users */}
|
||||||
|
{!isFirstRun && (
|
||||||
|
<motion.div {...fadeUp(0.24)} className="flex items-center gap-4 text-[11px] text-[var(--color-text-muted)]">
|
||||||
|
<span>
|
||||||
|
<kbd className="rounded border border-[var(--color-border)] px-1.5 py-0.5 font-mono text-[10px]">Ctrl+N</kbd>
|
||||||
|
{' '}new session
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<kbd className="rounded border border-[var(--color-border)] px-1.5 py-0.5 font-mono text-[10px]">Ctrl+K</kbd>
|
||||||
|
{' '}commands
|
||||||
|
</span>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user