diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx
index b83e1aef..adf3ff9f 100644
--- a/frontend/src/app/components/Layout/AppShell.tsx
+++ b/frontend/src/app/components/Layout/AppShell.tsx
@@ -936,13 +936,13 @@ const AppShell: React.FC = () => {
transform: sidePeek ? 'translateX(0)' : 'translateX(-118%)',
transition: 'transform 240ms cubic-bezier(0.22,1,0.36,1)',
pointerEvents: sidePeek ? 'auto' : 'none',
- } : {
- // Docked mode reads as a pill too: the dashboard-facing right corners curve; the window
- // edge keeps the left ones square (the OS rounds them with the window itself).
+ } : fsActive ? {
+ // Fullscreen with the sidebar pinned: the panel sits beside a full-bleed surface, so its
+ // dashboard-facing right corners curve like a pill; normal docked mode stays square.
borderRadius: '0 14px 14px 0',
overflow: 'hidden',
borderRight: `1px solid ${c.border.subtle}`,
- }),
+ } : {}),
}}
>
{/* Sidebar header = the app's chrome home (Arc/Zen): window-light clearance, back/forward, collapse, then the search command bar. */}
diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardEmptyState.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardEmptyState.tsx
index 608f43ca..7a73d3c6 100644
--- a/frontend/src/app/pages/Dashboard/canvas/DashboardEmptyState.tsx
+++ b/frontend/src/app/pages/Dashboard/canvas/DashboardEmptyState.tsx
@@ -32,6 +32,36 @@ function iconForStarter(text: string): LucideIcon {
return Sparkles;
}
+// The placeholder cycles agentic invitations with a typewriter feel (only while the field is empty),
+// so the hero reads like an agent offering to go DO things, not a search box waiting for keywords.
+const GHOST_DEFAULTS = [
+ 'Send an agent to find me something great...',
+ 'Build me a tool I can use right now...',
+ 'Research something and report back...',
+ 'Watch a site and tell me when it changes...',
+];
+
+function useTypedGhost(lines: string[], active: boolean): string {
+ const [out, setOut] = React.useState('');
+ React.useEffect(() => {
+ if (!active || lines.length === 0) return undefined;
+ let li = 0; let ci = 0; let deleting = false;
+ const id = window.setInterval(() => {
+ const line = lines[li % lines.length];
+ if (!deleting) {
+ ci += 1;
+ if (ci >= line.length + 24) deleting = true; // linger fully typed ~1s
+ } else {
+ ci -= 3;
+ if (ci <= 0) { deleting = false; ci = 0; li += 1; }
+ }
+ setOut(line.slice(0, Math.min(ci, line.length)));
+ }, 45);
+ return () => window.clearInterval(id);
+ }, [lines, active]);
+ return out;
+}
+
const DashboardEmptyState: React.FC<{
c: ClaudeTokens;
onLaunch?: (prompt: string, mode: string, model: string) => void;
@@ -41,8 +71,16 @@ const DashboardEmptyState: React.FC<{
const mode = useAppSelector((s) => s.settings.data.default_mode);
const canRun = useAppSelector((s) => hasFreeTrialActive(s) || hasModelConnected(s));
const personalized = useAppSelector((s) => s.settings.data.personalized_starters ?? []);
+ const userName = useAppSelector((s) => s.settings.data.user_name ?? null);
const [text, setText] = React.useState('');
const [launching, setLaunching] = React.useState(false);
+ const firstName = (userName ?? '').trim().split(/\s+/)[0] || null;
+ const headline = firstName ? `What should we get done, ${firstName}?` : 'What do you want done?';
+ const ghostLines = React.useMemo(
+ () => (personalized.length > 0 ? [...personalized.slice(0, 3).map((s) => `${s.title}...`), ...GHOST_DEFAULTS.slice(0, 2)] : GHOST_DEFAULTS),
+ [personalized],
+ );
+ const ghost = useTypedGhost(ghostLines, text.length === 0 && canRun);
const launch = (prompt: string) => {
const p = prompt.trim();
@@ -71,7 +109,7 @@ const DashboardEmptyState: React.FC<{
sx={{ width: '100%', maxWidth: 620, pointerEvents: 'auto', display: 'flex', flexDirection: 'column' }}
>
- What do you want done?
+ {headline}
{canRun && !!onLaunch ? (
@@ -98,7 +136,7 @@ const DashboardEmptyState: React.FC<{
onKeyDown={(e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); launch(text); setText(''); }
}}
- placeholder="Ask me anything..."
+ placeholder={ghost || "Ask me anything..."}
disabled={launching}
sx={{
flex: 1, border: 'none', outline: 'none', bgcolor: 'transparent',