mirror of
https://github.com/davidkaya/aryx.git
synced 2026-07-25 05:58:39 +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 = (
|
||||
<WelcomePane
|
||||
hasProjects={hasUserProjects}
|
||||
connectionStatus={sidecarCapabilities?.connection.status}
|
||||
onAddProject={() => void api.addProject()}
|
||||
onNewScratchpad={() => handleCreateScratchpad()}
|
||||
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 type { SidecarConnectionStatus } from '@shared/contracts/sidecar';
|
||||
import appIconUrl from '../../../assets/icons/icon.png';
|
||||
|
||||
interface WelcomePaneProps {
|
||||
hasProjects: boolean;
|
||||
connectionStatus?: SidecarConnectionStatus;
|
||||
onNewScratchpad: () => void;
|
||||
onAddProject: () => void;
|
||||
onOpenSettings: () => void;
|
||||
@@ -22,14 +24,19 @@ interface ActionCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
onClick: () => void;
|
||||
highlight?: boolean;
|
||||
}
|
||||
|
||||
function ActionCard({ icon, title, description, onClick }: ActionCardProps) {
|
||||
function ActionCard({ icon, title, description, onClick, highlight }: ActionCardProps) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
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">
|
||||
{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({
|
||||
hasProjects,
|
||||
connectionStatus,
|
||||
onNewScratchpad,
|
||||
onAddProject,
|
||||
onOpenSettings,
|
||||
}: 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 (
|
||||
<div className="relative flex h-full flex-col items-center justify-center overflow-hidden px-8">
|
||||
{/* 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 */}
|
||||
<motion.div {...fadeUp(0)}>
|
||||
<img
|
||||
@@ -82,22 +119,70 @@ export function WelcomePane({
|
||||
{/* Heading */}
|
||||
<motion.div {...fadeUp(0.08)}>
|
||||
<h1 className="font-display brand-gradient-text text-2xl font-bold tracking-tight">
|
||||
aryx
|
||||
{isFirstRun ? 'Welcome to Aryx' : 'aryx'}
|
||||
</h1>
|
||||
<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
|
||||
repo-aware Copilot agents.
|
||||
{isFirstRun
|
||||
? '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>
|
||||
</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 */}
|
||||
<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
|
||||
icon={<MessageSquarePlus className="size-4 text-white" />}
|
||||
title="New Scratchpad"
|
||||
description="Ask anything without a project context"
|
||||
title={isFirstRun ? 'Try a Quick Scratchpad' : 'New Scratchpad'}
|
||||
description={isFirstRun ? 'Start a conversation — no setup needed' : 'Ask anything without a project context'}
|
||||
onClick={onNewScratchpad}
|
||||
highlight={isConnected && isFirstRun}
|
||||
/>
|
||||
|
||||
{!hasProjects && (
|
||||
<ActionCard
|
||||
icon={<FolderPlus className="size-4 text-white" />}
|
||||
@@ -106,6 +191,7 @@ export function WelcomePane({
|
||||
onClick={onAddProject}
|
||||
/>
|
||||
)}
|
||||
|
||||
<ActionCard
|
||||
icon={<Settings className="size-4 text-white" />}
|
||||
title="Manage Patterns"
|
||||
@@ -113,6 +199,20 @@ export function WelcomePane({
|
||||
onClick={onOpenSettings}
|
||||
/>
|
||||
</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>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user