diff --git a/components/chat.tsx b/components/chat.tsx index e82e063..fd5b564 100644 --- a/components/chat.tsx +++ b/components/chat.tsx @@ -5,6 +5,7 @@ import { useChat } from "@ai-sdk/react"; import { lastAssistantMessageIsCompleteWithToolCalls } from "ai"; import { createChatTransport } from "@/components/chat-transport"; import { SetupDialog } from "@/components/setup-dialog"; +import { Hint } from "@/components/ui/hint"; import { useZeroKeys, ZeroKeysPill } from "@/components/setup-cta"; import { useStickToBottomContext } from "use-stick-to-bottom"; import { @@ -912,6 +913,7 @@ export function Chat({ onBlur={() => void commitRename(titleDraft)} spellCheck={false} aria-label="Session title" + title="Rename this session" className="lh-no-drag min-w-[8ch] max-w-[70%] shrink-0 bg-transparent font-title text-3xl tracking-tight caret-current outline-none [field-sizing:content]" /> {/* flex-1 spacer during rename too, so the chips stay at @@ -921,10 +923,15 @@ export function Chat({ ) : ( <> - + {/* flex-1 spacer: keeps the chips at the right edge AND stays a drag surface (no carve-out) — the freed bar is grippable. */} diff --git a/components/composer.tsx b/components/composer.tsx index 6034d7c..93b614b 100644 --- a/components/composer.tsx +++ b/components/composer.tsx @@ -2,6 +2,7 @@ import { CheckIcon, CornerDownLeftIcon, SquareIcon, XIcon } from "lucide-react"; import { cn } from "@/lib/utils"; +import { Hint } from "@/components/ui/hint"; /** * The chat composer — content row ONLY (the persistent rounded shell lives in @@ -108,13 +109,21 @@ export function Composer({ // pending on the next message; a side question now rides the // acceptance as its note instead).
- +
) : ( - + )} ); diff --git a/components/dictation.tsx b/components/dictation.tsx index 6f1cdda..20a8ce5 100644 --- a/components/dictation.tsx +++ b/components/dictation.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useRef, useState } from "react"; import { Loader2Icon, MicIcon } from "lucide-react"; import { useSettingsDialog } from "@/components/settings/settings-dialog"; +import { Hint } from "@/components/ui/hint"; /** * Voice dictation — the cloud engine (STT wave 1, 2026-09-17). @@ -481,37 +482,51 @@ export function DictationBar({ dictation }: { dictation: Dictation }) { if (!active) { return ( - + + {error ?? "Dictate"} + + ); } return (
- + Stop + + {/* Fixed width while recording — nothing ever appends text beside it (no interims, owner round 1). Cells carry no minimum width; the diff --git a/components/extraction-coverage.tsx b/components/extraction-coverage.tsx index bf572d9..f4e05fe 100644 --- a/components/extraction-coverage.tsx +++ b/components/extraction-coverage.tsx @@ -5,6 +5,7 @@ import { useActivity } from "@/components/activity-dock"; import { usePopoverDismiss } from "@/components/popover-dismiss"; import { useSettingsSaved } from "@/components/settings-signal"; import { formatTokens } from "@/components/usage-format"; +import { Hint } from "@/components/ui/hint"; import { timeAgo } from "@/components/sidebar"; /** @@ -290,18 +291,24 @@ export function CoverageRing({ chatId, refreshKey = 0 }: { chatId: string; refre const fill = ringFill(coverage.uncoveredTokens, graph.chunkTokens); return (
- + + {open && (
setOpen(false)); return (
- + {open && (
@@ -437,9 +443,13 @@ function GraphSettingsControl({ const physicsTuning = settings.physics; return (
- + {open && ( // Viewport-capped + internally scrollable — the coverage // dropdown's exact pattern (owner, 2026-09-17): max-h keeps a @@ -835,6 +846,12 @@ export function GraphChromeRow({ <> {!isEmpty && (
+
onQuery(e.target.value)} placeholder="Search nodes…" + aria-label="Search nodes" spellCheck={false} className="h-7 w-full rounded-lg border bg-background pl-8 text-xs outline-none placeholder:text-muted-foreground focus:border-ring" />
+
{noMatch && (
No matching nodes.
)} diff --git a/components/home.tsx b/components/home.tsx index 200e23b..cbd7a0c 100644 --- a/components/home.tsx +++ b/components/home.tsx @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"; import { CornerDownLeftIcon } from "lucide-react"; import { GraphView } from "@/components/graph-view"; import { SetupDialog } from "@/components/setup-dialog"; +import { Hint } from "@/components/ui/hint"; import { useStrongKeyless, useZeroKeys, ZeroKeysPill } from "@/components/setup-cta"; import { useDefaultSettings } from "@/components/default-settings"; import { DictationBar, useDictation } from "@/components/dictation"; @@ -176,7 +177,13 @@ export function Home({ initialSettings, initialConfigured }: { initialSettings: rows={1} className="field-sizing-content lh-input max-h-48 min-h-10 flex-1 resize-none bg-transparent px-2.5 py-2 outline-none placeholder:text-muted-foreground" /> - + {/* Same as the chat view's under-composer stats bar (owner) — no session yet, so the LEFT side renders nothing (wave 7 sweep) diff --git a/components/md-blocks.tsx b/components/md-blocks.tsx index 04a6c14..858eb1d 100644 --- a/components/md-blocks.tsx +++ b/components/md-blocks.tsx @@ -40,6 +40,7 @@ import type { ExtraProps } from "streamdown"; import { MERMAID_FONT_SIZE, renderMermaid } from "@/components/mermaid-render"; import { MermaidError } from "@/components/mermaid-error"; import { chartSize, useSessionMermaid } from "@/components/session-overlay"; +import { Hint } from "@/components/ui/hint"; // ─── The one affordance: the hover-reveal corner copy ──────────────────────── @@ -198,16 +199,22 @@ function MermaidInline({ chart }: { chart: string }) {
chart} label="Copy diagram source" /> {overlay && ( - + )}
diff --git a/components/model-picker.tsx b/components/model-picker.tsx index 984b7ee..5f4c02b 100644 --- a/components/model-picker.tsx +++ b/components/model-picker.tsx @@ -7,6 +7,7 @@ import { prettyModel } from "@/components/usage-format"; import { ModelMenuList, ThinkingMenuList } from "@/components/settings/model-menu"; import { composeSlotModel } from "@/components/settings/agent-section"; import { MenuShell } from "@/components/menu-shell"; +import { Hint } from "@/components/ui/hint"; import { notifySettingsSaved, useSettingsSaved } from "@/components/settings-signal"; import { defaultOrderLevel, populateLevel, THINKING_LABEL } from "@/components/thinking-default"; @@ -252,12 +253,18 @@ export function ModelControl() { return (
- - + {/* The shared dropdown body (extracted 2026-09-20): the agent settings' slot selects and the setup dialog use the exact same diff --git a/components/plan-block.tsx b/components/plan-block.tsx index 8522e39..7995f02 100644 --- a/components/plan-block.tsx +++ b/components/plan-block.tsx @@ -15,6 +15,7 @@ import { curveBasis, line } from "d3-shape"; import { fitGraphView, fitZoomFor, zoomedGraphView, type Viewport } from "@/components/pan-zoom"; import type { PlanBlockData, PlanNodeStatus, PlanTree } from "@/src/core"; import { derivedNodeStatus, derivePlanStatuses } from "@/src/core"; +import { Hint } from "@/components/ui/hint"; /** * PLAN BLOCKS — the plan lives in the chat (wave 4): persisted @@ -542,11 +543,12 @@ export function PlanBlock({ data, onOpen }: { data: PlanBlockData; onOpen: () => const statusMap = useMemo(() => new Map(data.nodes.map((n) => [n.id, n.status] as const)), [data]); const statusOf = (id: string): PlanNodeStatus => statusMap.get(id) ?? "upcoming"; return ( - + ); } @@ -745,7 +748,8 @@ export function PlanPopup({ messages, onClose }: { messages: readonly PopupMessa
The plan live — statuses derive from your check outcomes - +
Drag to pan · scroll to zoom
- - - +
diff --git a/components/session-overlay.tsx b/components/session-overlay.tsx index 84bf48c..cc581f8 100644 --- a/components/session-overlay.tsx +++ b/components/session-overlay.tsx @@ -6,6 +6,7 @@ import { cn } from "@/lib/utils"; import { fitGraphView, fitZoomFor, zoomedGraphView, type Viewport } from "@/components/pan-zoom"; import { renderMermaid } from "@/components/mermaid-render"; import { MermaidError } from "@/components/mermaid-error"; +import { Hint } from "@/components/ui/hint"; /** * SESSION-SCOPED OVERLAYS (owner wave, 2026-09-19): the "fullscreen" @@ -181,7 +182,8 @@ export function MermaidOverlay({ chart, onClose }: { chart: string; onClose: ()
Diagram from this lesson - +
Drag to pan · scroll to zoom
- - - +
diff --git a/components/settings/agent-section.tsx b/components/settings/agent-section.tsx index 0f07aae..6f5d99e 100644 --- a/components/settings/agent-section.tsx +++ b/components/settings/agent-section.tsx @@ -62,9 +62,9 @@ import { ModelMenuList, ThinkingMenuList } from "./model-menu"; /** One slot's display identity — the tier's roster as the row hint rides * with the resolved default (owner: the hint ALWAYS names the default). */ const SLOT_META: Record = { - strong: { label: "Strong", roster: "Prober + Teacher" }, - cheap: { label: "Cheap", roster: "Researcher, demo builder, extractor, auditor" }, - vision: { label: "Vision", roster: "Visual maker + PDF reader" }, + strong: { label: "Strong", roster: "The main model for your sessions" }, + cheap: { label: "Cheap", roster: "The assistants that research, build demos, and grade your work" }, + vision: { label: "Vision", roster: "The assistants that draw and read PDFs" }, }; /** The search engines this wave ships (src/core/search.ts) + their labels. */ @@ -403,7 +403,7 @@ export function AgentSection({
@@ -434,7 +434,7 @@ export function AgentSection({