Dashboard Overview
The screen an app opens on: shell, header, KPI row, chart and activity feed — numbers first, shape second, changes last.
An agent working inside a real app: a transcript down the middle — reasoning, answer, then the one card that can act — with what it may read and what it noticed demoted to a rail.
app/ai-assistant-page.tsxnpx hoverlab add ai-assistant-page
Or over MCP, from your editor's agent — no account needed.
Free to read, copy and install, for personal and non-commercial projects. Shipping it in client work or a paid product needs Pro ($79 once). The source lands in your repo and stops being ours — no attribution, nothing to upgrade.
Started 14 minutes ago · 4 tools used · reading 3 sources
Why did churn spike in Q3?
Churn concentrated in accounts under 20 seats, which lost 6.1% of ARR against 1.4% in the rest of the book 1 — see source 1. The common thread is activation rather than price: teams that never connected a second data source churned at roughly four times the rate of those that did 2 — see source 2. Only 11% of the cancellations cited cost in their exit survey, and most of those had already downgraded once 3 — see source 3.
Ask next
Waiting on you
I scored 214 accounts as at-risk for Q4. 38 of them cleared the high-confidence threshold. Sending to the full segment reaches more real churn but also more accounts that were never going to leave.
This sends real email from a production account.
Enter to send · Shift↵ for a new line
The real page, rendered in your current theme — every section below is a live block, not a screenshot.
Only want one section? Open it and copy that block instead — browse Dashboards.
Want the whole project rather than this one screen? A template ships every route, the theme tokens and the layout.
/**
* The assistant screen — a real agent working inside a real app.
*
* The composition is the lesson, and it is a different lesson from the
* dashboard's. A dashboard is panels beside panels; an assistant screen is a
* *transcript* down the middle, with everything else demoted to a rail. Get
* that hierarchy backwards — chat in a sidebar, panels in the centre — and
* the thing a user came to do is the smallest element on screen.
*
* The order down the transcript is the argument:
*
* question → reasoning → answer → the action it wants to take
*
* Reasoning sits between the question and the answer, collapsed. Below the
* answer it reads as a footnote nobody opens; above it, closed, the answer
* is still the first thing read but the working is one click away at the
* moment doubt appears rather than after the user has already acted. That is
* why the question is rendered here and `<ChatStreamingAnswer>` is given an
* empty one — the block draws its own bubble when used alone, and inside a
* thread that would print the same sentence twice.
*
* The approval card is last and is the only element that can change
* anything. That is deliberate: on this screen the agent can read whatever
* the rail permits and say whatever it likes, but the single point where it
* touches the world is a card the user has to answer.
*
* The rail carries what the transcript cannot: what the agent is allowed to
* read, and what it noticed that nobody asked about. Both are context for
* judging the transcript, which is why they are beside it rather than in it.
*
* The composer flows at the end of the transcript rather than being pinned.
* In a real full-height app it should be `sticky bottom-0`; here the shell
* is a fixed 32rem demo box, and a sticky composer inside a 512px scrollport
* covers the very turns this screen exists to show. Worth knowing before
* copying it into a layout that *is* full height.
*/
import * as React from 'react'
import { Bot, MessagesSquare, Sparkles, Wrench } from 'lucide-react'
import { DashboardShell } from '@/components/dashboard-shell'
import { AgentThinkingTrace } from '@/components/agent-thinking-trace'
import { ChatStreamingAnswer } from '@/components/chat-streaming-answer'
import { ApprovalRequestCard } from '@/components/approval-request-card'
import { ChatPromptBar } from '@/components/chat-prompt-bar'
import { KnowledgeSourcePicker } from '@/components/knowledge-source-picker'
import { AiInsightCards } from '@/components/ai-insight-cards'
const NAV = [
{ label: 'Assistant', icon: <Bot className="h-4 w-4" /> },
{ label: 'Threads', icon: <MessagesSquare className="h-4 w-4" />, badge: '3' },
{ label: 'Automations', icon: <Wrench className="h-4 w-4" /> },
]
export default function AiAssistantPage() {
return (
<DashboardShell brand="Acme Copilot" nav={NAV} activeLabel="Assistant">
<div className="grid gap-6 xl:grid-cols-[minmax(0,1fr)_20rem]">
{/* -- The transcript ------------------------------------------- */}
<div className="min-w-0">
<header className="mb-5 flex items-center gap-3">
<span className="flex h-9 w-9 shrink-0 items-center justify-center rounded-xl bg-primary/10 text-primary">
<Sparkles aria-hidden className="h-4 w-4" />
</span>
<div className="min-w-0">
<h1 className="truncate text-lg font-semibold tracking-tight">
Q3 churn review
</h1>
<p className="truncate text-xs text-muted-foreground">
Started 14 minutes ago · 4 tools used · reading 3 sources
</p>
</div>
</header>
{/* The turn being answered. Rendered here so the reasoning can
sit between it and the reply. */}
<p className="mb-4 text-right text-sm">
<span className="inline-block rounded-2xl rounded-br-md bg-primary px-4 py-2.5 text-left text-primary-foreground">
Why did churn spike in Q3?
</span>
</p>
{/* Closed by default: available at the moment of doubt, not in the
way of the answer. */}
<AgentThinkingTrace defaultOpen={false} className="!max-w-none !p-0" />
<div className="mt-3">
<ChatStreamingAnswer question="" className="!max-w-none !p-0" />
</div>
{/* The one element on this screen that can change anything. */}
<div className="mt-6">
<ApprovalRequestCard className="!max-w-none" />
</div>
<div className="mt-6 border-t border-border/60 pt-4">
<ChatPromptBar className="!max-w-none !p-0" />
</div>
</div>
{/* -- The rail --------------------------------------------------- */}
<aside className="min-w-0 space-y-6" aria-label="Assistant context">
<AiInsightCards className="!max-w-none !p-0" />
<KnowledgeSourcePicker className="!max-w-none !p-0" />
</aside>
</div>
</DashboardShell>
)
}
components/ — each block page has its own copy button.app/ai-assistant-page.tsx. The imports already point at @/components/…, so they resolve with no edits.The component, its props, the design tokens it expects and the command that installs it — as one prompt. Paste it into Claude, Cursor, v0 or ChatGPT and what they build around it will match the rest of the catalog instead of inventing its own system.
The screen an app opens on: shell, header, KPI row, chart and activity feed — numbers first, shape second, changes last.
A kanban board inside the app shell, with tabs that admit the board is one view among several and a header whose primary action is creating work.
The canonical CRUD list — header, toolbar, sortable table and pagination framed as one panel instead of three stacked cards.
A wizard for the session someone finishes and a checklist for the one they do not, with the only genuinely blocking step in front of both.
Built around the state a search page is in most of the time — empty — so recent queries lead and the facets sit in a sidebar that survives growing to twelve.
One run opened up in the order you would debug it — intent, actions, the retries that are invisible in both, then cost.
The queue, the same queue sorted by how long things have waited, and the policy that decided both — with the policy last so it is not scrolled past forever.
The blank thread answered with real starter prompts, the model picker with the three numbers the choice turns on, and the four surfaces a session grows into — attachments, branches, canvas, threads.
The three states an agent screen usually omits: the wait before the first token, the trace after something broke with retries costed in the open, and the diff it wants a person to accept.
Why the answer said what it said: what is connected, how stale it is, what the query was allowed to see, what fitted in the window and what fell out — plus the failed-search state a debugging tool is most needed in.
Five ways to reach a model without the cursor leaving the sentence — slash menu, inline completion, selection toolbar, action menu, property inspector — and the two surfaces that say where the answer came from.
A table after real people have used it — columns reordered from the keyboard, a selection bar that asks page-or-all, subtotals that survive collapsing, cells edited in place, and the empty state its own filters produced.
The saved view and the comparison window first, because every figure below depends on both — then six charts, none of which load a charting library.
Thresholds with how often each has actually fired, what that produced, where it went — and the digest settings that exist because of the answer.
The flow between an empty product and a useful one, in the order it goes wrong: sample data offered with equal weight, a file, a URL, the column mapping everything stalls on, and an upload that survives the connection dropping.
A profile form where the role options state what they actually change, the avatar crop works from the keyboard, and a navigation guard names the fields it is about to throw away.
The furniture that is on every screen and belongs to none — navbar, mobile drawer, command palette, shortcut sheet, header search, scope switcher, and the two shapes a detail view takes without losing the list.
Backwards from now and forwards from it on one screen — a day-grouped timeline, a month grid where a busy Tuesday never changes the row height, and the state both are in on day one.
What the product looks like while onboarding happens: a tour step anchored to the control it explains, the skeletons of the slowest load it will ever do, and the checklist that outlives both.