/** * WorkingHints — soft, rotating product-feature tips shown while the agent * is streaming (and there's no PacingPill already occupying the slot). * * Why: the user is otherwise just watching tokens land. A subtle hint every * few seconds surfaces features they might not know about — @mentions, * snapshots, the DB tab, slash commands. Strictly informational. Hint * pool comes from real shipped features; if a feature changes name, this * is the one place to update. * * Behavior: * - Visible whenever `isStreaming` is true — INCLUDING during a pacing * wait, since that's when the user has the most idle time staring at the * screen. Stacks below the PacingPill; the two are complementary. * - First hint shown immediately on streaming start (no awkward blank). * - Rotates every ROTATE_MS while still streaming. * - Pre-shuffled order — cycles through ALL hints before repeating. */ import { useEffect, useRef, useState } from 'react' import useAppStore from '../store/appStore' const ROTATE_MS = 7_500 // Real, shipped features only. Keep each tip ≤ 80 chars so the row fits on // a narrow chat column without truncation. const HINTS = [ { icon: '@', text: 'Type @ to pin a file — the agent always reads it on that turn.' }, { icon: '⌘K', text: '⌘K opens the command palette: run, restart, switch session, find files.' }, { icon: '/', text: 'Slash commands: /run, /stop, /restart, /reload, /snapshot, /clear-logs.' }, { icon: '/', text: '/explain, /fix, /refactor, /test, /style — templated prompts for the active file.' }, { icon: '✦', text: 'Hover an error line in the Logs tab to get a "Fix with AI" pill.' }, { icon: '📷', text: 'Snapshot before a risky refactor — one click to roll back if it goes sideways.' }, { icon: '⛁', text: 'The DB tab inspects your SQLite live — schema, row counts, paginated data.' }, { icon: '⌬', text: 'The Terminal tab is a real shell rooted at your project — npm / uv / git all work.' }, { icon: '⚡', text: 'Auto-pacing keeps you under the free-tier rate limit — no error toasts.' }, { icon: '⌥', text: 'Click the model name (left to Run icon) to switch — your choice is saved to your profile.' }, { icon: '↻', text: 'Edits hot-reload via Vite HMR / node --watch / uvicorn --reload — no manual restart.' }, { icon: '◆', text: 'Four templates: React+Express, FastAPI+React, Next.js+Prisma, API-only FastAPI.' }, { icon: '⌘B', text: '⌘B toggles the sidebar; the file tree lives there with AI-tracked badges.' }, { icon: '⏎', text: 'Shift+Enter for a newline. Enter alone sends. Esc aborts a running turn.' }, { icon: '✎', text: 'The agent uses apply_patch (unified diff) for small edits — cheaper than rewrites.' }, { icon: '↗', text: 'Open-in-browser (TopBar ↗) launches the running app in a real browser tab.' }, ] export default function WorkingHints() { const isStreaming = useAppStore((s) => s.isStreaming) // Start at a stable index; the effect picks the (random) order at run time. // Doing Math.random in useState/useMemo trips React's "no impure functions // during render" rule. const [idx, setIdx] = useState(0) const orderRef = useRef([]) useEffect(() => { if (!isStreaming) return // Build a fresh shuffled order at the start of each streaming session so // we cycle through every hint before repeating. const a = HINTS.map((_, i) => i) for (let i = a.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)) ;[a[i], a[j]] = [a[j], a[i]] } orderRef.current = a let pos = 0 setIdx(a[pos]) const id = setInterval(() => { pos = (pos + 1) % a.length setIdx(a[pos]) }, ROTATE_MS) return () => clearInterval(id) }, [isStreaming]) if (!isStreaming) return null const hint = HINTS[idx] return (