Home
The approvals column and its 20rem right rail: what needs a decision, forecast, meetings and tasks.
What it's for#
Home answers one question when someone opens Oration in the morning: what needs me today. Decisions agents are waiting on come first; the day's numbers and meetings sit beside them.
It is a document page with a two-column grid. The left column is a queue of proposals from agents, each one an approval with its reasoning. The right rail is glanceable: the quarter's forecast against target, today's meetings and today's tasks. Nothing on Home is a dashboard of metrics for their own sake; every card leads to something to do.
| Route | Role |
|---|---|
| / | Oration GTM Home: the greeting, Needs your call, the forecast, meetings and tasks. |
| /ai | The Agents Platform home on the same grid: a greeting, a stat strip, needs attention on the left and live calls in the rail. |
| /reports | Where the forecast card's meta link leads. |
| /meetings | Where Today's meetings leads. |
| /tasks | Where Due today leads, and where new tasks are added. |
Live composition#
Maya Okafor's Home on Monday, September 28, drawn at 1280px and scaled. Click a card, then press J and K to move, A to approve and S to skip. Approving shows a toast with Undo.
import { AgentAvatar } from "@oration/canon/components/agent-avatar";import { Button } from "@oration/canon/components/button";import { Checkbox } from "@oration/canon/components/checkbox";import { Empty, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle } from "@oration/canon/components/empty";import { Kbd } from "@oration/canon/components/kbd";import { Meter } from "@oration/canon/components/meter";import { Tag } from "@oration/canon/components/tag";import { Textarea } from "@oration/canon/components/textarea";import { Timeline, TimelineHeader, TimelineItem, TimelineTime } from "@oration/canon/components/timeline";import { toast } from "@oration/canon/components/toast";import { cn } from "@oration/canon/lib/utils";import { ArrowRightIcon, ArrowUpRightIcon, CheckCheckIcon, CoinsIcon, MailIcon, PencilIcon, SparkleIcon, SparklesIcon, TrendingUpIcon, VideoIcon,} from "lucide-react";import { AnimatePresence, motion } from "motion/react";import * as React from "react";import { AppShell } from "../document-page/frame";export function HomePage() { type Approval = { id: string; agent: { id: string; name: string; role: string }; title: string; summary: string; reasoning: string; createdAt: string; approveLabel: string; done: string; preview?: { to: string; subject: string; body: string }; change?: { field: string; from: string; to: string }; cost?: number; }; const seed: Approval[] = [ { id: "ap_1", agent: { id: "a_quill", name: "Quill", role: "Outreach writer" }, title: "Reply to Jordan Lee with the 3-year option", summary: "Northwind Freight asked for 3-year pricing with the vendor portal before Friday's review.", reasoning: "Jordan asked two direct questions. The draft answers both, links the updated order form and proposes a 20-minute walkthrough on Oct 1.", createdAt: "7:06 AM", approveLabel: "Approve and send", done: "Email scheduled to send at 9:30 AM", preview: { to: "Jordan Lee", subject: "Re: Pricing for 22k invoices a month", body: "Hi Jordan,\n\nHere's the 3-year option with the vendor portal as its own line. Year one is $186,000 and years two and three hold flat.\n\nWould 20 minutes on Wednesday help before Friday?\n\nMaya", }, }, { id: "ap_2", agent: { id: "a_tally", name: "Tally", role: "Pipeline hygiene" }, title: "Move Orchard Street to Commit", summary: "Legal returned redlines this morning and Wen Zhou is named as signer.", reasoning: "Exit criteria for Negotiation are met: redlines received and signer named. The Oct 23 close date still holds.", createdAt: "8:42 AM", approveLabel: "Approve", done: "Deal updated", change: { field: "Forecast", from: "Best case", to: "Commit" }, }, { id: "ap_3", agent: { id: "a_atlas", name: "Atlas", role: "Research" }, title: "Enrich 140 accounts from the FinOps Summit list", summary: "Fill industry, headcount, finance systems and ICP fit for new event leads.", reasoning: "The list was imported Friday with only name and domain. 38 of 140 already match a company and will be skipped.", createdAt: "6:15 AM", approveLabel: "Approve", done: "Enrichment started", cost: 280, }, { id: "ap_4", agent: { id: "a_tally", name: "Tally", role: "Pipeline hygiene" }, title: "Re-engage Halcyon before the deal goes cold", summary: "No reply from Tomás Ferreira in 20 days. Close date is Oct 31.", reasoning: "Suggests moving the close date to Dec 15 and creating a call task for Priya on Wednesday.", createdAt: "Yesterday", approveLabel: "Approve", done: "Close date moved and task created", change: { field: "Close date", from: "Oct 31", to: "Dec 15" }, }, ]; const [approvals, setApprovals] = React.useState(seed); const [activeId, setActiveId] = React.useState<string | null>("ap_1"); const [editing, setEditing] = React.useState(false); const [draft, setDraft] = React.useState(""); const [tasks, setTasks] = React.useState([ { id: "t_1", title: "Send 3-year pricing to Jordan", done: false }, { id: "t_2", title: "Review Q4 commit with Priya", done: false }, { id: "t_3", title: "Confirm the signer at Orchard Street", done: true, }, ]); const active = approvals.find((item) => item.id === activeId); const won = 612_000; const commit = 540_000; const best = 380_000; const target = 1_800_000; const max = Math.max(target, won + commit + best); const money = (value: number) => value >= 1_000_000 ? `$${(value / 1_000_000).toFixed(value % 100_000 ? 2 : 1)}M` : `$${Math.round(value / 1000)}K`; const resolve = (item: Approval, approved: boolean) => { const index = approvals.findIndex((a) => a.id === item.id); const rest = approvals.filter((a) => a.id !== item.id); setApprovals(rest); setEditing(false); setActiveId(rest[Math.min(index, rest.length - 1)]?.id ?? null); toast.add({ title: approved ? item.done : "Skipped", description: approved ? `${item.agent.name} will log the change on the record.` : `${item.agent.name} won't act on this. You can find it in the agent's run log.`, type: approved ? "success" : undefined, actionProps: { children: "Undo", onClick: () => { setApprovals((list) => { const next = [...list]; next.splice(index, 0, item); return next; }); setActiveId(item.id); }, }, }); }; const onKeyDown = (event: React.KeyboardEvent) => { const target = event.target as HTMLElement; if (target.closest("input, textarea, [contenteditable=true]")) return; if (event.metaKey || event.ctrlKey || event.altKey) return; const index = approvals.findIndex((a) => a.id === activeId); if (event.key === "j" || event.key === "k") { event.preventDefault(); const delta = event.key === "j" ? 1 : -1; const next = approvals[ Math.min(approvals.length - 1, Math.max(0, index + delta)) ]; if (next) { setActiveId(next.id); setEditing(false); } } if (event.key === "a" && active) { event.preventDefault(); resolve(active, true); } if (event.key === "s" && active) { event.preventDefault(); resolve(active, false); } if (event.key === "e" && active?.preview) { event.preventDefault(); setDraft(active.preview.body); setEditing(true); } }; return ( <AppShell active="Home" crumbs={[{ label: "Home" }]} actions={ <Button variant="outline" size="sm" onClick={() => toast.add({ title: "Ask an agent", description: "The Copilot opens beside the page.", }) } > <SparklesIcon data-icon="inline-start" aria-hidden="true" /> Ask an agent </Button> } > <main className="min-h-0 flex-1 overflow-y-auto" onKeyDown={onKeyDown} > <div className="mx-auto w-full max-w-6xl px-4 py-8 @min-[640px]:px-6"> <div> <h1 className="text-2xl font-semibold tracking-[-0.02em]"> Good morning, Maya </h1> <p className="mt-1 text-sm text-muted-foreground"> Monday, September 28.{" "} {approvals.length ? `${approvals.length} ${approvals.length === 1 ? "decision" : "decisions"} waiting` : "No decisions waiting"}{" "} and 3 meetings on your calendar. </p> </div> <div className="mt-8 grid gap-8 @min-[1024px]:grid-cols-[minmax(0,1fr)_20rem] @min-[1024px]:grid-rows-[auto_1fr_auto] @min-[1024px]:gap-y-4"> <aside aria-label="Today at a glance" className="grid content-start gap-4 @min-[768px]:grid-cols-2 @min-[1024px]:col-start-2 @min-[1024px]:row-start-1 @min-[1024px]:grid-cols-1" > <section aria-labelledby="home-forecast" className="rounded-xl bg-card p-4 shadow-border" > <div className="flex items-baseline justify-between"> <h2 id="home-forecast" className="text-sm font-semibold" > Q4 2026 forecast </h2> <span className="inline-flex items-center gap-0.5 text-xs text-muted-foreground"> Reports <ArrowUpRightIcon aria-hidden="true" className="size-3" /> </span> </div> <p className="mt-1 text-13 text-muted-foreground"> <span className="font-medium text-foreground tabular-nums"> {money(won + commit)} </span>{" "} won or committed against a{" "} <span className="tabular-nums"> {money(target)} </span>{" "} target. </p> <Meter className="mt-4" size="lg" max={max} target={target} label="Forecast against target" segments={[ { label: "Closed won", value: won, className: "bg-ink-80", }, { label: "Commit", value: commit, className: "bg-ink-45", }, { label: "Best case", value: best, className: "bg-ink-15", }, ]} /> <div aria-hidden="true" className="relative mt-1.5 h-4 text-2xs text-muted-foreground" > <span className="absolute -translate-x-full whitespace-nowrap tabular-nums" style={{ left: `${(target / max) * 100}%`, }} > Target {money(target)} </span> </div> <dl className="mt-3 flex flex-col gap-1.5"> {[ ["Closed won", won, "bg-ink-80"], ["Commit", commit, "bg-ink-45"], ["Best case", best, "bg-ink-15"], ].map(([label, value, swatch]) => ( <div key={String(label)} className="flex items-center gap-x-2 text-13" > <span aria-hidden="true" className={cn( "size-2 rounded-sm", String(swatch), )} /> <dt className="text-muted-foreground"> {label} </dt> <dd className="ml-auto font-medium tabular-nums"> {money(Number(value))} </dd> </div> ))} <div className="mt-1 flex items-center gap-2 border-t border-border pt-2 text-13"> <dt className="text-muted-foreground"> Gap to target after commit </dt> <dd className="ml-auto font-medium tabular-nums"> {money(target - won - commit)} </dd> </div> </dl> <p className="mt-3 text-xs text-muted-foreground"> 94 days left in the quarter </p> </section> <section aria-labelledby="home-meetings" className="rounded-xl bg-card shadow-border" > <div className="flex items-baseline justify-between px-4 pt-4 pb-2"> <h2 id="home-meetings" className="text-sm font-semibold" > Today's meetings </h2> <span className="text-xs text-muted-foreground"> All meetings </span> </div> <ul className="flex flex-col px-2 pb-2"> {[ [ "9:30 AM", "Northwind Freight pricing review", 30, ], [ "11:00 AM", "Halcyon security review", 45, ], [ "2:30 PM", "Orchard Street redlines", 30, ], ].map(([time, title, minutes]) => ( <li key={String(title)}> <button type="button" onClick={() => toast.add({ title: String(title), description: `Starts at ${time}. The join link opens in a new tab.`, }) } className="group flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left outline-none transition-colors duration-150 hover:bg-muted focus-visible:ring-3 focus-visible:ring-ring/40" > <span className="w-14 shrink-0 text-xs text-muted-foreground tabular-nums"> {time} </span> <span className="flex min-w-0 flex-1 flex-col"> <span className="truncate text-13 font-medium"> {title} </span> <span className="text-xs text-muted-foreground tabular-nums"> {minutes} min </span> </span> <VideoIcon aria-hidden="true" className="size-3.5 text-muted-foreground opacity-0 transition-opacity duration-150 group-hover:opacity-100" /> </button> </li> ))} </ul> </section> </aside> <section aria-labelledby="needs-your-call" className="min-w-0 @min-[1024px]:col-start-1 @min-[1024px]:row-span-2 @min-[1024px]:row-start-1" > <div className="mb-3 flex items-baseline justify-between gap-4"> <h2 id="needs-your-call" className="text-sm font-semibold text-foreground" > Needs your call <span className="ml-2 font-normal text-muted-foreground tabular-nums"> {approvals.length} </span> </h2> <p className="hidden items-center gap-1.5 text-xs text-muted-foreground @min-[768px]:flex"> <Kbd>J</Kbd> <Kbd>K</Kbd> to move, <Kbd>A</Kbd> to approve, <Kbd>S</Kbd> to skip </p> </div> {approvals.length === 0 ? ( <Empty className="rounded-xl bg-card py-12 shadow-border"> <EmptyHeader> <EmptyMedia variant="icon"> <CheckCheckIcon /> </EmptyMedia> <EmptyTitle> You're all caught up </EmptyTitle> <EmptyDescription> When an agent needs a decision, the proposal shows up here with its reasoning. </EmptyDescription> </EmptyHeader> </Empty> ) : ( <ul className="flex flex-col gap-2"> {approvals.map((item) => { const open = item.id === activeId; return ( <li key={item.id}> <article aria-labelledby={`${item.id}-title`} className={cn( "rounded-xl bg-card shadow-border transition-shadow duration-200 ease-out", open ? "shadow-border-hover" : "hover:shadow-border-hover", )} > <button type="button" aria-expanded={open} onClick={() => { setActiveId( item.id, ); setEditing(false); }} className="flex w-full items-start gap-3 rounded-xl px-4 py-3 text-left outline-none focus-visible:ring-3 focus-visible:ring-ring/40" > <AgentAvatar agent={item.agent} size={32} className="mt-0.5" /> <span className="min-w-0 flex-1"> <span className="flex items-center gap-2 text-xs text-muted-foreground"> <span className="font-medium text-foreground"> { item .agent .name } </span> <span> { item .agent .role } </span> <span className="ml-auto tabular-nums"> { item.createdAt } </span> </span> <span id={`${item.id}-title`} className="mt-0.5 block text-sm font-medium text-pretty" > {item.title} </span> {open ? null : ( <span className="mt-0.5 block truncate text-13 text-muted-foreground"> { item.summary } </span> )} </span> </button> <AnimatePresence initial={false} > {open ? ( <motion.div key="body" initial={{ height: 0, opacity: 0, }} animate={{ height: "auto", opacity: 1, }} exit={{ height: 0, opacity: 0, }} transition={{ duration: 0.22, ease: [ 0.23, 1, 0.32, 1, ], }} className="overflow-hidden" > <div className="flex flex-col gap-3 px-4 pb-4 @min-[640px]:pl-[3.75rem]"> <p className="text-13 text-pretty"> { item.summary } </p> {item.preview ? ( <div className="rounded-[10px] bg-muted/70"> <div className="flex items-center gap-2 border-b border-border px-3 py-2 text-xs text-muted-foreground"> <MailIcon aria-hidden="true" className="size-3.5" /> To{" "} { item .preview .to } <span className="truncate font-medium text-foreground"> { item .preview .subject } </span> </div> {editing ? ( <div className="flex flex-col gap-2 p-2"> <Textarea aria-label="Email body" value={ draft } onChange={( event, ) => setDraft( event .target .value, ) } className="min-h-32 border-0 bg-background text-[13px] shadow-none" /> <div className="flex justify-end gap-1.5"> <Button variant="ghost" size="sm" onClick={() => setEditing( false, ) } > Cancel </Button> <Button variant="outline" size="sm" onClick={() => { setApprovals( ( list, ) => list.map( ( a, ) => a.id === item.id && a.preview ? { ...a, preview: { ...a.preview, body: draft, }, } : a, ), ); setEditing( false, ); }} > Save draft </Button> </div> </div> ) : ( <p className="px-3 py-2.5 text-13 whitespace-pre-line"> { item .preview .body } </p> )} </div> ) : null} {item.change ? ( <div className="flex flex-wrap items-center gap-2 rounded-[10px] bg-muted/70 px-3 py-2 text-13"> <span className="text-muted-foreground"> { item .change .field } </span> <Tag className="line-through decoration-foreground/40"> { item .change .from } </Tag> <ArrowRightIcon aria-label="changes to" className="size-3.5 text-muted-foreground" /> <Tag color="indigo"> { item .change .to } </Tag> </div> ) : null} {item.cost ? ( <div className="flex items-center gap-2 rounded-[10px] bg-muted/70 px-3 py-2 text-13"> <CoinsIcon aria-hidden="true" className="size-4 text-muted-foreground" /> <span> <span className="font-medium tabular-nums"> { item.cost }{" "} credits </span> <span className="text-muted-foreground"> {" "} of 4,120 left this period </span> </span> </div> ) : null} <p className="flex gap-2 text-13 text-muted-foreground"> <SparkleIcon aria-hidden="true" className="mt-0.5 size-3.5 shrink-0" /> <span> <span className="sr-only"> Reasoning:{" "} </span> { item.reasoning } </span> </p> <div className="flex flex-wrap items-center gap-1.5 pt-1"> <Button onClick={() => resolve( item, true, ) } > { item.approveLabel } </Button> {item.preview && !editing ? ( <Button variant="outline" onClick={() => { setDraft( item .preview ?.body ?? "", ); setEditing( true, ); }} > <PencilIcon data-icon="inline-start" aria-hidden="true" /> Edit </Button> ) : null} <Button variant="ghost" onClick={() => resolve( item, false, ) } > Skip </Button> </div> </div> </motion.div> ) : null} </AnimatePresence> </article> </li> ); })} </ul> )} </section> <div className="@min-[1024px]:col-start-2 @min-[1024px]:row-start-2 @min-[1024px]:self-start"> <section aria-labelledby="home-tasks" className="rounded-xl bg-card shadow-border" > <div className="flex items-baseline justify-between px-4 pt-4 pb-2"> <h2 id="home-tasks" className="text-sm font-semibold" > Due today </h2> <span className="text-xs text-muted-foreground"> All tasks </span> </div> <ul className="flex flex-col px-2 pb-2"> {tasks.map((task) => ( <li key={task.id} className="flex items-start gap-2.5 rounded-lg px-2 py-1.5" > <Checkbox checked={task.done} aria-label={`Complete ${task.title}`} className="mt-0.5" onCheckedChange={(checked) => { setTasks((list) => list.map((t) => t.id === task.id ? { ...t, done: checked, } : t, ), ); if (checked) toast.add({ title: "Task completed", actionProps: { children: "Undo", onClick: () => setTasks( ( list, ) => list.map( ( t, ) => t.id === task.id ? { ...t, done: false, } : t, ), ), }, }); }} /> <span className={cn( "min-w-0 flex-1 text-13", task.done && "text-muted-foreground line-through", )} > {task.title} </span> <span className="shrink-0 text-xs text-muted-foreground"> Today </span> </li> ))} </ul> </section> </div> <section aria-labelledby="since-yesterday" className="min-w-0 @min-[1024px]:col-start-1 @min-[1024px]:row-start-3 @min-[1024px]:mt-6" > <h2 id="since-yesterday" className="mb-4 text-sm font-semibold" > Since yesterday </h2> <Timeline> <TimelineItem icon={<TrendingUpIcon />} tone="success" > <TimelineHeader> <strong>Priya Raman</strong> <span>moved Halcyon to Proposal</span> <TimelineTime> Yesterday, 4:12 PM </TimelineTime> </TimelineHeader> </TimelineItem> <TimelineItem icon={<SparklesIcon />} tone="primary" > <TimelineHeader> <strong>Atlas</strong> <span> enriched 62 contacts at Northwind Freight </span> <TimelineTime> Today, 6:02 AM </TimelineTime> </TimelineHeader> </TimelineItem> </Timeline> </section> </div> </div> </main> </AppShell> );}Home builds its approval cards by hand rather than with Approval card, so the collapsed rows can share one expanded body and the queue can move with J and K. The composition here draws the same card from Canon parts. Use Approval card anywhere a single proposal stands alone.
Anatomy#
Nine parts. The left column is for deciding, the rail for glancing.
- App header. Crumbs read Home. The one header action is an outline Ask an agent button, so the filled button stays with the approvals.
- Greeting. The only place Display (600, 24px, -0.02em) is used. The summary line under it counts decisions and meetings in plain words.
- Needs your call. A Title heading with the count in tabular figures, and the J, K, A and S hint in Kbd from 768px.
- Expanded approval. The active card lifts to the hover shadow and opens its body: summary, the email or field change in a tint well, credits, reasoning and actions. It holds the view's only filled button.
- Collapsed approvals. Agent, role, time, title and a one-line summary. Clicking or pressing J and K makes one active; only one is open at a time.
- Forecast. A Meter of closed won, commit and best case in Ink 80, 45 and 15, with a 2px target marker and a Footnote label under it, then a legend and the gap to target.
- Today's meetings. Time in a 56px tabular column, title and length. Rows fill Well Gray on hover and reveal a join icon.
- Due today. Up to five tasks with a Checkbox. Completing one strikes it through and offers Undo.
- Since yesterday. A Timeline of what changed overnight, under the approvals from 1024px.
Measurements#
Home is a document page, so the column, gutters and padding are the document page's. These are the parts it adds.
| Measure | Value | Classes |
|---|---|---|
| Column | 72rem | mx-auto w-full max-w-6xl px-4 py-8 sm:px-6 |
| Greeting to grid | 32px | mt-8 |
| Rail width from 1024px | 20rem | lg:grid-cols-[minmax(0,1fr)_20rem] |
| Between columns | 32px | gap-8 |
| Between rail cards | 16px | gap-4, lg:gap-y-4 |
| Between approval cards | 8px | gap-2 |
| Card padding | 16px; 12px rows in collapsed cards | p-4, px-4 py-3 |
| Expanded body indent | 60px from 640px | sm:pl-[3.75rem], under the title |
| Forecast meter | 10px track | Meter size="lg" |
| Target label | 11px | Footnote, text-2xs |
Responsive behavior#
Below 1024px the rail moves above the approvals and sits two-up from 768px. The source order never changes, only the grid placement.
From 1024px the rail moves to a 20rem right column, 32px from the approvals, with 16px between its cards. Due tasks join it under the meetings.
| Width | What changes |
|---|---|
| 390px | One column: forecast, meetings, approvals, due tasks, then the timeline. The key hint is hidden. The expanded body loses its indent. |
| 768px | The forecast and meetings sit two-up above the approvals (md:grid-cols-2). |
| 1024px | Two columns. The rail is 20rem on the right, with due tasks under the meetings, and the timeline moves under the approvals. |
| 1440px | The 72rem column centers and stops growing. The rail stays 20rem. |
States#
Each region owns its states. An empty queue is good news and says so.
| Region | States it owns |
|---|---|
| Greeting | Counts change with the queue. With nothing waiting it says No decisions waiting. |
| Needs your call | Active and collapsed cards, editing a draft, approved and skipped (with Undo in the toast), and empty: You're all caught up, in Empty. |
| Forecast | Below target, at target and over target; the target label flips to the left of its marker past 85%. |
| Today's meetings | A row per meeting with its time and length; hover reveals the join icon. No empty state yet. |
| Due today | Open, and done with a strike-through and an Undo toast. |
Home reads its data synchronously, so none of its regions draw a loading or error state today, and the meetings and tasks cards have no empty state. Canon's data states pattern asks each card for all three. Add them with Data state when the cards read from a query.
Keyboard#
Home is built to clear the queue without a mouse. The keys are single letters, ignored while typing or while a menu or dialog is open.
| Keys | Action |
|---|---|
| J | Make the next approval active. |
| K | Make the previous approval active. |
| A | Approve the active proposal. |
| S | Skip the active proposal. |
| E | Edit the draft, when the proposal has one. |
- In the product the keys listen on
window. In the composition above they listen on the frame, so click inside it first. - J and K don't animate the move. Keyboard-driven changes are instant; see Keyboard.
Starter code#
The grid that places the rail first in the source and second on screen.
// app/(app)/page.tsxexport default function HomePage() { return ( <> <AppHeader crumbs={[{ label: "Home" }]} actions={<HomeHeaderActions />} /> <div className="mx-auto w-full max-w-6xl px-4 py-8 sm:px-6"> <div> <h1 className="text-2xl font-semibold tracking-[-0.02em]">Good morning, Maya</h1> <p className="mt-1 text-sm text-muted-foreground"> Monday, September 28. 4 decisions waiting and 3 meetings on your calendar. </p> </div> <div className="mt-8 grid gap-8 lg:grid-cols-[minmax(0,1fr)_20rem] lg:grid-rows-[auto_1fr_auto] lg:gap-y-4"> {/* The rail comes first in the source, so it is first for keyboard and screen-reader users at every width. */} <aside aria-label="Today at a glance" className="grid content-start gap-4 md:grid-cols-2 lg:col-start-2 lg:row-start-1 lg:grid-cols-1" > <ForecastCard /> <TodayMeetings /> </aside> <div className="min-w-0 lg:col-start-1 lg:row-span-2 lg:row-start-1"> <ApprovalsQueue /> </div> <div className="lg:col-start-2 lg:row-start-2 lg:self-start"> <DueTasks /> </div> <section aria-labelledby="since-yesterday" className="min-w-0 lg:col-start-1 lg:row-start-3 lg:mt-6"> <h2 id="since-yesterday" className="mb-4 text-sm font-semibold">Since yesterday</h2> <ActivityTimeline items={overnight} /> </section> </div> </div> </> );}// A rail card: a Title and a 12px meta link on one baseline, then a list inset 8px<section aria-labelledby="home-meetings" className="rounded-xl bg-card shadow-border"> <div className="flex items-baseline justify-between px-4 pt-4 pb-2"> <h2 id="home-meetings" className="text-sm font-semibold">Today's meetings</h2> <Link href="/meetings" className="text-xs text-muted-foreground hover:text-foreground">All meetings</Link> </div> <ul className="flex flex-col px-2 pb-2">…</ul></section>