The provider
vendo init never writes a client file. It prints one paste at the end of the
run, naming your layout and the exact lines.
baseUrl is where the route is mounted, path prefix included. A deployment
served under /maple passes baseUrl="/maple/api/vendo".
The theme cast narrows TypeScript’s widened JSON string literals. If init
also wrote .vendo/fonts.css, import it beside the theme so generated screens
carry your brand font wherever your own stylesheet does not reach.
The props
The overlay
The default drop-in: a launcher pill in the corner that opens the conversation panel. On phones it becomes a full-bleed takeover below 768px. The pill is white-label. It reads “AI agent” until you give it your own label and mark.
position takes any viewport corner. offset nudges the whole cluster inward
when your own UI already lives in that corner, and launcher="none" hides the
pill so you can drive the panel yourself.
Where the panel sits
placement="center" is the default: a centered box over a scrim, with the
page behind it inert.

The default placement. Maple's home is still there, dimmed and out of reach behind the panel.
placement="dock" is the side-panel posture. The panel holds the right edge
full height and your page reflows beside it, staying visible and clickable.
dockWidth is also the amount the page reflows by, so the two can never
disagree. It defaults to 420.
Conversations survive
A reload resumes the conversation the user was in, mid-stream replies included. The panel’s header lists earlier conversations to pick back up. None of that needs wiring. A remembered conversation that no longer exists falls back to a fresh start on its own.Drive it yourself
useVendoOverlay gives you open, close, toggle, and a fresh conversation,
plus the props to hand back to the overlay.
thread prop is the one sanctioned injection point. Pass your own thread
component and the overlay stays the shell: portal, scrim, focus, and the
mobile sheet.
The command palette
⌘K on macOS, Ctrl+K elsewhere, opens the same conversation panel in its
compact form.
onCommand and every chip activation routes through you instead. Typed
text that matches no command is simply the message.
The binding is a singleton no matter how many palettes mount, and it never
steals a keystroke from a focused input of yours. Pass a chord
(hotkey={{ key: "j", meta: true }}), a matcher function, or false to wire
your own.

Slots
A slot is a place in your own page that a generated view can occupy. It comes from@vendoai/vendo itself — nothing extra to install.
children you passed come back, never a
blank hole — but they also stand in for the invitation, so a slot that should
invite has none.


label is what a person reads when they pick one, and it
defaults to the id read as words.
A report ages out, though, and a product whose agent is the whole surface never
renders a <VendoSlot> to make one. Declare those instead:
description is optional — it’s the sentence an agent reads to pick between
two slots a label alone can’t separate. A declared slot needs no render, never
decays, and beats a page report of the same id, so no one’s page can rewrite
what you authored.
Declaring one makes it a destination, not a display. The pin lands, but a person
sees what is pinned there only where some page renders a <VendoSlot> with that
id — so a product that renders none still needs to mount one somewhere before a
pinned dashboard is visible to anybody.
To make a pin land somewhere, name the destination on the provider with
pinSlot. With one slot the panel shows Pin to dashboard; with several,
the same button becomes a picker of your slots.
Triggers
A button anywhere in your product that hands one task to the agent. It fills the composer and never sends, so it is safe on any prompt.context rides along so the agent starts oriented on what is on screen. For
your own element instead of this button, call openVendoConversation({ prompt }).
The bare thread
VendoThread is the conversation with no shell: composer, streaming replies,
tool chips, approval cards, and generated views arriving inline. Use it when
the conversation belongs inside your own layout.
Tool labels
Chrome renders a chip every time the agent calls a tool, and an approval card when a call needs a decision. Without configuration it prettifies the raw id, sohost_email_send reads as “Email send”.
Pass your own copy through the provider’s tools prop for user-facing
surfaces.
formatField is display only. The raw arguments still drive the decision, so
formatting a field cannot change what the user is approving.
Approvals on a generated screen
When someone presses a button inside a generated view and that press needs approval, the ask arrives as a modal centered over the page. Presses queue, and exactly one modal is on screen at a time. Escape and the scrim close it without deciding, so the ask comes back on the next press. The modal is mounted for you insideVendoSlot, in-thread app cards, and the
panel. The decision itself is server-side: see
what stops a call.
Theming
The token file every surface above reads, and how init fills it from your
own brand.
Generated apps
What a generated view can do, who owns it, and how it gets its data.