"use client"; import { AnimatePresence, LayoutGroup, MotionConfig, motion, useReducedMotion } from "motion/react"; import { ArrowLeftIcon, ArrowRightIcon, ArrowUpIcon, BookOpenIcon, BotIcon, CheckIcon, ChevronRightIcon, ChevronsUpDownIcon, FileTextIcon, GlobeIcon, InboxIcon, LibraryIcon, LoaderCircleIcon, LockIcon, type LucideIcon, MenuIcon, PanelRightIcon, PaperclipIcon, PlusIcon, RotateCwIcon, SearchIcon, SquareTerminalIcon, WrenchIcon, XIcon, } from "lucide-react"; import { type KeyboardEvent, type PointerEvent, type ReactNode, useCallback, useEffect, useId, useRef, useState, } from "react"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { Kbd } from "@/components/ui/kbd"; import { Tabs, TabsList, TabsPanel, TabsTab } from "@/components/ui/tabs"; import { Tooltip, TooltipPopup, TooltipTrigger } from "@/components/ui/tooltip"; import { cn } from "@/lib/utils"; export interface WorkspaceTask { id: string; title: string; state: "running" | "done" | "idle"; time: string; } export interface WorkspaceToolCall { id: string; tool: string; detail: string; icon: LucideIcon; ms: number; output?: string; } export interface WorkspaceSource { id: string; title: string; domain: string; cited: number; tone: string; } export interface SplitShellProps { tasks?: WorkspaceTask[]; toolCalls?: WorkspaceToolCall[]; sources?: WorkspaceSource[]; defaultPanelWidth?: number; minPanelWidth?: number; maxPanelWidth?: number; children?: ReactNode; className?: string; } const spring = { type: "spring", stiffness: 420, damping: 38 } as const; const defaultTasks: WorkspaceTask[] = [ { id: "t1", title: "Competitive scan: note apps", state: "running", time: "now" }, { id: "t2", title: "Draft Q4 pricing memo", state: "done", time: "1h" }, { id: "t3", title: "Summarise churn interviews", state: "done", time: "Tue" }, { id: "t4", title: "Onboarding email rewrite", state: "idle", time: "Mon" }, { id: "t5", title: "Clean up the roadmap doc", state: "idle", time: "Oct 18" }, ]; const defaultToolCalls: WorkspaceToolCall[] = [ { id: "c1", tool: "web.search", detail: "note taking app pricing 2026", icon: SearchIcon, ms: 820, output: "14 results · 6 kept" }, { id: "c2", tool: "browser.open", detail: "quillpad.app/pricing", icon: GlobeIcon, ms: 1640, output: "Read 3 plan cards, 1 FAQ" }, { id: "c3", tool: "browser.open", detail: "notably.io/plans", icon: GlobeIcon, ms: 1310, output: "Read 4 plan cards" }, { id: "c4", tool: "files.read", detail: "churn-interviews.md", icon: FileTextIcon, ms: 410, output: "38 quotes tagged 'price'" }, { id: "c5", tool: "python.run", detail: "compare_plans.py", icon: SquareTerminalIcon, ms: 2280, output: "Table: 3 apps × 5 columns" }, { id: "c6", tool: "doc.write", detail: "Draft summary", icon: BookOpenIcon, ms: 1900, output: "412 words" }, ]; const defaultSources: WorkspaceSource[] = [ { id: "s1", title: "Quillpad · Plans and pricing", domain: "quillpad.app", cited: 4, tone: "bg-orange-500/14 text-orange-800 dark:text-orange-400" }, { id: "s2", title: "Notably plans for teams", domain: "notably.io", cited: 3, tone: "bg-teal-500/14 text-teal-800 dark:text-teal-400" }, { id: "s3", title: "Driftnote changelog: October", domain: "driftnote.co", cited: 2, tone: "bg-info/14 text-info-foreground" }, { id: "s4", title: "churn-interviews.md", domain: "Orbit files", cited: 5, tone: "bg-violet-500/18 text-violet-700 dark:text-violet-400" }, { id: "s5", title: "The state of note apps, 2026", domain: "fieldnotes.blog", cited: 1, tone: "bg-rose-500/14 text-rose-700 dark:text-rose-400" }, ]; function Sidebar({ tasks, onClose }: { tasks: WorkspaceTask[]; onClose?: () => void }) { const [active, setActive] = useState(tasks[0]?.id); const group = useId(); return (
Orbit Workspace {onClose && ( )}
Tasks {tasks.length}
DA
Dara Achebe
Northwind Labs
); } const compare = [ { app: "Quillpad", price: "$8", seats: "Unlimited", ai: "Add-on $5", offline: true }, { app: "Notably", price: "$10", seats: "Up to 50", ai: "Included", offline: false }, { app: "Driftnote", price: "$6", seats: "Up to 20", ai: "None", offline: true }, ]; const quotes = [ { text: "We paid for Notably's AI on every seat and maybe three people used it.", who: "Ops lead, 30-person agency" }, { text: "Half our week is on client sites with bad wifi. Offline was the deal breaker.", who: "Producer, events studio" }, ]; const enter = { initial: { opacity: 0, y: 4 }, animate: { opacity: 1, y: 0 }, transition: { duration: 0.22, ease: [0.23, 1, 0.32, 1] } } as const; function Cite({ n, label }: { n: number; label: string }) { return ( } > {n} {label} ); } function SkeletonLines({ rows, label }: { rows: number; label: string }) { return (
{label} {Array.from({ length: rows }, (_, i) => ( ))}
); } function SampleThread({ running, step, total }: { running: boolean; step: number; total: number }) { const [draft, setDraft] = useState(""); const composer = useId(); const scroller = useRef(null); const reduce = useReducedMotion(); const haveTable = step >= 3; useEffect(() => { const el = scroller.current; if (!el || step === 0) return; const t = setTimeout(() => { const nearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 240; if (nearBottom) el.scrollTo({ top: el.scrollHeight, behavior: reduce ? "auto" : "smooth" }); }, 60); return () => clearTimeout(t); }, [step, reduce]); const haveQuotes = step >= 4; return (
Scan the three note apps we lose deals to. Pricing, seat limits, AI and offline. Use the churn interviews for why people leave.
{running ? ( ) : ( )} {running ? `Working · step ${Math.min(step + 1, total)} of ${total}` : `Finished · ${total} steps · 9.4s`}

Notably costs the most per seat but bundles{" "} AI , {" "} and paying for AI nobody used is the top reason teams left. Quillpad is the closest match on seats and still works{" "} offline .

{haveTable ? (
{compare.map((r) => ( ))}
App Per seat Seats AI Offline
{r.app} {r.price} {r.seats} {r.ai} {r.offline ? ( ) : ( — )}
    {compare.map((r) => (
  • {r.app} {r.price} / seat {r.seats} seats · AI {r.ai.toLowerCase()} · {r.offline ? "works offline" : "online only"}
  • ))}
) : ( )}

Why teams left

{haveQuotes ? ( {quotes.map((q) => (
  • “{q.text}” {q.who}{" "}
  • ))}
    ) : ( )}
    {!running && (

    I saved the write-up to Research. Want a one-slide version for Thursday's pricing review?

    Note apps: competitive scan Doc · 412 words · 5 sources
    )}
    { e.preventDefault(); setDraft(""); }} >