The shared component vocabulary: the shadcn/ui roster Cinatra ships, the design rules each primitive inherits, and the host-provided composite steps. Part of Cinatra Application Design — the tokens, surfaces and accents these components map to live in that reference. Everything here is normative; match it exactly.
Cinatra ships shadcn/ui primitives with the design tokens of the Application Design reference mapped to their semantic variables. Use the components from @/components/ui directly — do not re-skin them inline. Each component below is a brief on its role and the design rules it inherits; the roster closes with the host-provided Standard scheduling step composite.
<PageHeader /> · @cinatra-ai/sdk-ui · @/components/page-header
The page-header h1 owns the page-title typography end-to-end — Archivo italic 800, -0.018em tracking, 1.05 line-height, balanced wrap — so pages never roll their own. The title scale is lg 28px (the default — every top-level page), md 24px (action-heavy admin / settings / detail subpages with long titles), sm 20px (nested sub-screens); the scale is monotonic — a smaller step never renders larger than the one above it. Title tone is ink by default; brand-mustard is a per-route opt-in for brand-y top-level pages, never on settings or admin chrome. An optional mono uppercase kicker label sits above the h1, an optional one-line description below it. The etched paired-line rule closes the header on every page — unless a toolbar sits directly beneath, which replaces the rule (see Toolbar).
<SectionHeader />, <Kicker /> · @cinatra-ai/sdk-ui
The section header is the in-page counterpart of the page header: an optional mono uppercase kicker, a title, an optional one-line description, and an optional right-aligned actions slot that holds status pills, buttons or a close control. Its title takes the headline treatment every section title already inherits — Archivo italic 800 on the tight title tracking, balanced wrap, in ink (Application Design, VIII.4 and VIII.7) — in three sizes: sm 20px, md 24px (the default) and lg 30px. It is the page title that opens the page; a section header only subdivides what the page already opened, so a page carries exactly one page header and as many section headers as it has sections.
The kicker is the canonical uppercase mono label above a section title, a table head or a card topline. (The sidebar group label is its own primitive, drawn below with its own brief, and is not a kicker.) It carries two sizes, xs 10px (the default) and sm 12px, and three tracking lanes, each a named type token rather than a per-call-site value: 0.18em for the standard section and sidebar label, 0.2em for roomier labels (settings groups, empty-state tags), and 0.3em for the page-context label above a page title.
<Button /> · @/components/ui/button
7 variants · 5 sizes · cva
Primary, default, outline, secondary, destructive, ghost, link. Sizes: default, xs, sm, lg, icon (xs/sm/lg). Indigo primary, ink default border, destructive uses red-on-tint not solid red.
<Card /> · @/components/ui/card
Non-interactive cards use --surface. Clickable cards (agent tiles, run rows, popovers, anything with hover or focus) use --surface-strong per rule #8.
<Input />, <Textarea /> · @/components/ui/input
Always pure white background. Border is the strong navy hairline. Focus ring picks up --ring (indigo). Use InputGroup for compound inputs with addons.
<Select />, <DropdownMenu /> · @/components/ui/select
Trigger mirrors Input chrome. Open popover sits on --surface-strong with the same hairline border, slightly higher shadow. Use scrollbar-thin on long lists.
<Dialog />, <Sheet /> · @/components/ui/dialog
Modal dialogs use --paper — the same background as pages; right-side sheets sit at full-height. Overlay top: 4rem so it doesn't cover the navbar. Dialog header uses an etched paired-line rule for separation.
<Badge />, <LifecycleBadge /> · @/components/ui/badge
Status pills (see V) use bg tinted from the status colour, text in the same colour, border at higher alpha. Use icon-led pills; never just dots.
Badge and the status pill are two different jobs on one geometry, and the split is normative. Badge is the plain label chip — a raw label carrying no status meaning (a count, a version, a category, a "New"), drawn in the label variants above: the filled default, the hairline outline, the muted secondary, and the red-on-tint destructive. The status roster never rides on a Badge. Anything that stands for a run, approval or lifecycle state takes the status pill and its fixed roster (V), so one state is never drawn two ways on two screens. Where a surface is specified to carry a connection or lifecycle chip of its own, it is drawn in the spec that owns that surface — see for example Application Design — Connectors.
<Tabs />, <TabsList />, <TabsTrigger /> · @/components/ui/tabs
Tab labels are 13px sans, slate when inactive, indigo when selected with a 2px indigo underline. No pill tabs; underline only. When tabs appear under a section heading, the etched paired-line rule begins to the right of the last tab and stretches to the page edge — the tablist takes the left portion of the row, the rule the right.
<Toolbar /> · @/components/ui/toolbar (composition)
Controls inside a toolbar must look different from standalone ones: they carry no individual border. The toolbar ground is their container, so a button, tab, or field placed inside it drops the border (and the fill) it would have on its own and reads only as a tinted region of the bar. A horizontal bar that groups related view controls — tablist, search, primary view action. The toolbar provides the chrome, so controls inside it carry no individual borders: the toolbar ground is their container. Hover and selected states are background tints on a rounded rectangle (7px radius, same geometry as the search pill); the font weight and colour never change between states. Vertical hairlines (24px tall, centred) separate logical groups: between the tablist and search, and between search and the action button. The search field keeps its white interactive surface — the only white element inside the toolbar. The toolbar sits directly below the page header and replaces the section rule for that view — never stack a toolbar and the etched paired rule.
<Toolbar /> → <Toolbar.Child /> · @/components/ui/toolbar (composition)
When a selected toolbar item has its own sub-controls, they open in a child toolbar directly beneath the parent rather than in a menu — and that child can in turn carry a third. Each level inherits every toolbar rule (borderless controls, hairline group separators, the lone white search field) and adds three of its own. Ground steps lighter with depth — the parent sits on the darkest chrome (var(--toolbar)) and each child lightens ~6L toward the page, so the deepest row reads as the one nearest the content it edits. The lightening stops short of the paper: even the deepest ground stays a clear step darker than the page, so every level remains legible as its own bar. Each child is inset 20px from the level above and stacked 6px beneath it, so the active path reads top-to-bottom at a glance — the inset and the lightening carry the lineage; no connectors are drawn. Cap the depth at three. A fourth level means the navigation is too deep for a toolbar — move it into the page body or a sidebar instead. Only one item per level is selected, and selecting a different item replaces every level below it.
<Sidebar />, <AppSidebar /> · @/components/ui/sidebar
Cinatra's sidebar from app-sidebar.tsx. Mustard fedora + Archivo wordmark in the brand head. Active item is indigo bg at 6% alpha; section labels are 10px mono uppercase.
The rail carries three states, and only three. Expanded is the resting state — the full-width rail with the brand head, the group labels and every item label. Collapsed is the icon-only rail: the labels drop, the brand head falls back to the fedora alone, and the group labels go with them; the icons keep their order and their active tint so the collapse is reversible by eye. Mobile sheet is the narrow-viewport form — the same rail, at sheet width, slid in over a dimmed page instead of holding a column; it is the expanded rail's content, never a third layout.
<SidebarGroupLabel /> · @/components/ui/sidebar
Group labels in the sidebar — the small uppercase mono-spaced tag that sits above a cluster of nav items (Intelligence, Management, Information, Tools). Slate-muted slate at 10px with 0.18em letter-spacing reads as a quiet section break rather than a clickable item. Always above its group, never indented to match its children.
<Tooltip />, <Popover /> · @/components/ui/tooltip
Tooltips are navy with cream type; popovers are surface-strong with navy text. Tooltips read at 12px; popovers at 13–14px.
<Avatar />, <AvatarImage />, <AvatarFallback /> · @/components/ui/avatar
User avatars carry the user's initials in Archivo italic 800 on a random categorical accent (never indigo or navy). Per-extension instances same pattern (see IV).
<Form />, <Field />, <Label /> · @/components/ui/form
Form scaffolding — labels above inputs, helper or error text below. Errors swap helper text colour to brand red and add aria-invalid to the input.
<Checkbox />, <RadioGroup />, <Switch /> · @/components/ui
Single-select (radio), multi-select (checkbox), and instant-binary (switch). Indigo for active state. Switches are reserved for immediate-effect settings; checkboxes for confirmable form choices.
<Alert />, <AlertDialog /> · @/components/ui/alert
Inline alerts for warnings and errors; AlertDialog for destructive confirmations. Use the destructive variant (red) for terminal failures, warning (mustard) for cautions, info (indigo) for neutral notices.
Five variants, no more: default on the card surface for a notice that carries no status at all, then the four status variants — info (indigo), success (sea-green), warning (mustard) and destructive (brand red). The default sits on the card surface with a plain hairline. Each status variant is one recipe at its own colour — background tinted from that colour, border at higher alpha, and the icon in the colour itself — never a solid status fill behind body text. The three colour rows above show that ground and border treatment at info, success and warning; the worked destructive example above them shows the anatomy every variant shares, where the title stays ink and the description stays muted so the alert body reads as ordinary text on a tinted ground.
<Sonner />, toast() · @/components/ui/sonner
Transient confirmations and async progress. Five variants — default · success · warning · error · info — each on the popover surface with a status-coloured border and matching text; an icon on the left identifies the type. Every toast carries a Copy action and a Close (X) on the right, so the user can grab the message text or dismiss the toast. Never block on a toast; use Dialog when the user must acknowledge. Stack vertically, newest on top.
<Table />, <PaginatedTable /> · @/components/ui/table
PaginatedTable handles offset, sort, and selection; Table is the dumb DOM primitive. Headers always uppercase mono with the navy underline; never centre body cells; right-align numerics and timestamps.
<Command />, <CommandDialog /> · @/components/ui/command
Global cmd+K palette and inline pickers. Section labels in 10px mono; rows at 13px; the selected row uses the indigo soft-tint background, not a border.
<Breadcrumb /> · @/components/ui/breadcrumb
Always in the topbar. Three to four crumbs maximum; truncate the middle with an ellipsis if longer. Never combine with tabs.
The trail is the navigation hierarchy. A breadcrumb always reflects the navigation hierarchy — the route the page sits on, not the thing the page happens to be about. Every trail under the agents area starts with “Agents”; under an agent instance the trail names the agent’s display name, as it is written there (“Agents › Blog Draft Writer Agent (1)”), and the page that starts a run reads “Agents › Agent run”, never “Run agent” alone. A review has no trail of its own: there is no review page view outside the route of the agent’s run, so “Agents › Agent run › Review” is not a possible breadcrumb — the review is read on its run’s own route, under that run’s trail. And an id never stands where a name belongs: “Agents › aced3514… › Review” fails twice over — an id in the place of the agent’s name, and a hierarchy the routes do not have. What a crumb stands for, and the one narrow reading in which part of an id stands in while a name is genuinely unavailable, are below.
A page that is not found has no hierarchy. If a page is not found, then that page has no hierarchy — and so no trail to draw. Its breadcrumb reads “Page not found” and nothing else: one crumb, current, with no parent above it. A trail like “Agents › Agent run” over a page that was not found makes no sense — it names a place the reader never reached. This reading draws no entity crumb at all, so the carve-out below — which fixes what a crumb standing for a refused or missing entity may show — reads on the readings that still draw a trail.
A crumb that stands for an entity id shows that entity's display name — at every position, not only the last. Names come from the owning page's server render, strictly after its access checks; the shell never fetches a name from the client after paint. While a name is genuinely unavailable, the crumb shows the id's first eight characters plus an ellipsis ("9c0dfce6…") — never a title-cased raw id. Visiting a not-found or not-authorized surface clears every remembered name before paint, so a previously seen name can never survive into an unauthorized view. On those two readings the placeholder rule does not apply: a crumb standing for the refused or the missing entity shows a fixed label naming the surface ("Agent run") and never any part of the id — a truncated id is still an id, and no part of one reaches a reader who may not read the entity it names. The browser-tab title mirrors the resolved trail under the same rules: an id-bearing route never shows a raw id in the tab.
<Pagination /> · @/components/ui/pagination
For long lists and tabular data. Active page is ink-filled; surrounding pages are line-bordered on white. Always pair with a "X of N" caption in mono slate.
<Skeleton />, <Spinner /> · @/components/ui/skeleton
Skeletons mirror the layout they're replacing; never use a global spinner overlay when a skeleton would do. Spinner only for short (<500ms) inline waits inside buttons or icons.
<Empty /> · @/components/ui/empty
Use whenever a list, table, or section has zero content. Always include a single primary action button — never just empty text.
<Accordion />, <Collapsible /> · @/components/ui/accordion
Use for sectioned settings panels and FAQ-style content. Default to single-open; allow multi-open only when items are truly independent.
<Separator /> · @/components/ui/separator
Section breaks use the etched paired-line (the spec's signature divider). It stretches edge-to-edge of the content column by default. If a tablist is present in the same row, the rule starts immediately to the right of the last tab and runs to the page edge — never overlap a tablist with the rule, and never stack them. If a toolbar sits below the page header, the toolbar replaces the section rule entirely; never stack both.
<Slider /> · @/components/ui/slider
Single-value and range. Track is the muted surface, the filled portion indigo, the thumb a white interactive disc with an indigo ring (white per rule #8). Pair with a mono value readout; never label the thumb itself.
<Progress /> · @/components/ui/progress
Determinate for known-length work (a run with N steps), indeterminate for open-ended waits. Same geometry as the slider track. Show the percentage in mono above the bar; never render text inside it.
<Toggle />, <ToggleGroup /> · @/components/ui/toggle
A two-state button. Pressed state is the indigo soft-tint with ink/indigo content; rest is transparent with slate content. As a single-select group it becomes a segmented control — the canonical way to choose one of two or more options (the List / Board / Timeline view switcher above); as a multi-select group it toggles several at once (the formatting icons). Either way the buttons share one outer border with hairlines between segments — no gaps, exactly one or any number pressed. Use for view modes and inline formatting, not for settings that take immediate effect (use Switch).
<Calendar />, <DatePicker /> · @/components/ui/calendar
Month grid on the white popover surface. Weekday heads are mono uppercase; the selected day is an indigo-filled disc; today carries an indigo ring; a date range fills the in-between days with the indigo soft-tint. DatePicker is the calendar inside a Popover triggered from an Input-styled button.
<Combobox /> · @/components/ui/combobox
A Select crossed with the Command menu: an Input-chrome trigger opens a type-to-filter list. The current value carries an indigo check; the highlighted row uses the indigo soft-tint. Reach for it over Select whenever the option count passes ~8.
<HoverCard /> · @/components/ui/hover-card
A preview card on hover — for people, agents, and links. Sits on the white popover surface with a softer, longer open delay than a tooltip (≈700ms). Use when the preview carries structure (an avatar, stats); for a one-line hint use Tooltip instead.
<ContextMenu />, <DropdownMenu /> · @/components/ui/context-menu
Right-click context menus and the chevron DropdownMenu share this surface. Rows highlight on the indigo soft-tint; keyboard shortcuts sit right-aligned in mono slate; destructive items take brand red. Group with the 1px hairline, never the etched paired rule.
<Menubar /> · @/components/ui/menubar
A persistent horizontal menu bar (desktop-app style) on the toolbar ground. The open trigger takes the indigo soft-tint; its dropdown reuses the context-menu surface. Reserve it for dense editor surfaces; most product chrome should prefer the Toolbar or a DropdownMenu.
<NavigationMenu /> · @/components/ui/navigation-menu
Top-level site/product navigation with optional mega-panels. The active top link takes the 2px indigo underline (same as Tabs); the dropdown panel is a white card holding a two-column grid of titled links. Distinct from the left Sidebar — use this only for marketing or horizontal product shells.
<ScrollArea /> · @/components/ui/scroll-area
Replaces the OS scrollbar inside scrollable panels — command lists, sidebars, long popovers. A 6px overlay track with a low-alpha navy thumb that fades when idle. Same scrollbar-thin treatment referenced by Select.
<InputOTP /> · @/components/ui/input-otp
One-time-code and verification entry. Each digit is a 40px white slot with the strong navy border; the focused slot picks up the indigo ring and a blinking caret. Digits set in mono. Split groups with a short navy dash, never a vertical line.
<Drawer /> · @/components/ui/drawer
A bottom-anchored sheet for mobile and touch flows. Carries the grab handle, white surface, and a 14px top radius; the overlay dims everything below the 4rem navbar like Dialog. On desktop prefer the right-side Sheet; reserve Drawer for narrow viewports.
<ResizablePanelGroup /> · @/components/ui/resizable
Draggable split panes for master-detail layouts (a runs list beside a run detail). The handle is the strong navy hairline with a small grip; it brightens to indigo while dragging. Supports nested horizontal and vertical groups.
<AspectRatio /> · @/components/ui/aspect-ratio
Locks a child (image, embed, map, chart) to a fixed width-to-height ratio so layouts don't jump before media loads. Unfilled, it shows the seersucker-striped placeholder with a mono label naming the expected ratio. Clip the child with the 8px radius.
<Carousel /> · @/components/ui/carousel
Horizontal slide track for stat tiles, onboarding steps, or media. Arrows are white interactive discs placed outside the frame so they never cover content; the dot pager marks the current slide with an elongated indigo pill. Keep slide counts low and never auto-advance critical content.
<ChartContainer /> · @/components/ui/chart
A thin wrapper over Recharts with the tokens pre-mapped: indigo for the primary series (faded indigo for prior periods), sea-green for positive trends and brand red for negative, the navy hairline for gridlines, and mono ticks. Keep charts quiet — one or two series, no drop shadows or gradients beyond a faint area fill.
Host-provided composite · run scheduling gate + the run's Schedule step · 3 modes · 2 readings of one form
Scheduling is a platform default: every agent can run immediately, once at a future time, or on a repeating schedule, with no scheduling declaration of its own. One host-provided standard scheduling step renders the choice — in the run's first step when an agent opens on the scheduling gate, and again on the run's Schedule step once a future or repeating run is set. It is the same form both times, never a summary of itself. It supersedes the retired per-agent trigger embed: an agent no longer carries a hand-copied scheduling subflow, its own confirm gate, or a version-pinned trigger dependency. A definition may still carry a metadata flag that disables or restricts scheduling, but that flag is metadata only — never a form or subflow copy.
Run scheduling gate · when-to-run choice + date-time / recurring builder + timezone + Continue
The step is the run's first gate whenever an agent opens un-triggered: the workspace content is replaced by this form until a choice is made and submitted. Under the heading When should this run? sit three selectable rows — Run right after setup, Schedule for later, and Recurring. Schedule for later reveals a Run at date-time field and its own Timezone select; Recurring reveals a structured builder — Repeat every N day(s) / week(s) / month(s) / quarter / year, the frequency-appropriate selectors beneath it (weekday toggles for weekly; a day-of-month or nth-weekday choice for monthly, quarterly and yearly; a quarter anchor; a month), an At hour:minute time-of-day, and its own Timezone select. There is no raw cron field — the host compiles the builder's selections into the stored 5-field cron expression. An Estimated run duration line sits above the actions, and submitting Continue arms the trigger and advances the run — there is no separate confirm step. When the run carries a natural-language prompt, the always-visible assistant panel pre-fills these controls (the loading state); a validation failure — a past instant, or a recurring schedule with no time chosen — surfaces inline on the field (the error state).
/agents · the run's Schedule step, once a schedule is set · the same form + Save changes · Cancel schedule for a recurring schedule
Once a scheduled or recurring schedule is set, the run keeps a Schedule step in its step rail, and opening it shows this same form — the same heading, the same three option rows on the values that are set, the same Estimated run duration — and nothing else. There is no read-only summary panel, no Steps held until trigger fires list beside it and no Adjust to press before the form appears: the form is the reading, and changing a row is how the schedule is changed. Right-aligned controls close it. Save changes re-arms the schedule and is quiet until a row actually changes, because there is nothing to save until then. A recurring schedule carries one control beside it — Cancel schedule, which stops the recurring schedule and then leaves the scheduler no longer editable, and which confirms through an AlertDialog before it fires. A one-off schedule never carries it, and there is no Run now. Once a Run right after setup or Schedule for later schedule has fired it cannot be changed any more: the form stays as a read-only reading with no controls at all. A recurring schedule that has fired is not spent — it keeps these same editable rows, the same Save changes and the same Cancel schedule, and a change applies to the runs still to come. The same form, in the same readings, is what a schedule stated in a conversation is drawn as.
Three modes, one required control each. Every mode records an IANA timezone; the stored shape is the platform's per-run schedule row — no per-agent copy.
| Mode | Required control | Stored as |
|---|---|---|
| Run right after setup | — (none) | triggerType: immediate |
| Schedule for later | Run at — a future date & time | scheduledAt (ISO 8601 UTC) · timezone |
| Recurring | Recurring builder — frequency, interval, day/weekday & time-of-day | cronExpression (host-compiled) · timezone |
The previous approach delivered scheduling as a per-agent renderer chain: a hand-inlined configure subflow, a separate read-only confirm gate, and an exact-version trigger dependency pinned into every embedding agent. That chain is retired. The confirm gate is removed — the in-flow step arms the trigger directly on Continue — and no agent declares a trigger renderer or pins a trigger dependency. What stays is exactly the platform machinery it always ran on: the per-run schedule storage, the schedule firing, and the run's Schedule step with its Save changes control and, for a recurring schedule, Cancel schedule. The change is only that the in-flow renderer is now host-standard, available to every agent for free rather than copied into each one.