Velloo documentation
Markdown for LLMs ↗Port an existing page
Code-to-design. Seed the theme from your app's stylesheet, rebuild the structure on the canvas, and verify fidelity against the live URL.
The reverse direction: your app already has pages, and you want them on the canvas, so redesigns happen in Velloo instead of in scattered JSX edits, and the design becomes the source going forward.
The guiding rule is re-express, don’t clone. A ported screen strips handlers, state, and data fetching, inlines representative copy as literals, and keeps the visual structure. Designs are static by construction; a data-heavy dashboard ports as its structure plus fixture copy.
1. Seed the theme
Match the palette before composing anything, or every color decision you make will be against the wrong background. import_theme reads the app’s stylesheet. Note that globals.css lives outside the design folder, so pass an absolute path or one relative to the host app root (css takes the stylesheet text instead):
{ "tool": "import_theme", "args": { "cssPath": "app/globals.css" } }
What it parses:
- shadcn-convention
:root/.darkcustom properties: raw HSL triplets or any CSS color - Tailwind v4
@theme--color-*variables, withvar()indirection resolved, plus--radiusand--font-*roles tailwind.configtheme.extend(found near thecssPath): brandcolorsland in the theme’spalette, namedspacingbecomes spacing tokens (sow-icon-railresolves),boxShadowbecomes shadows,fontFamilybecomes font roles,keyframes+animationbecome--animate-*
Beyond the semantic slots, every other custom color var (numeric scales like --primary-600, extra roles like --success-500, bare brand names like --ink) is captured into the palette.* passthrough namespace, so app classes like bg-primary-600 or bg-ink render literally on the canvas instead of falling back. The result also reports the app’s Tailwind container config as container.suggestedClasses. Centered/padded/capped layout doesn’t transfer as a theme, so wrap page content in a Box with those classes.
It dry-runs by default; review the changes list, then re-run with apply: true. Read coverage as well as the change count: an app whose variables follow its own naming rather than shadcn’s maps zero semantic slots, parks every color in palette.*, and leaves colors.background, primary, and the rest on the starter palette. Map those yourself with set_theme { tokens } before composing. Details in the theme guide.
Pages behind a login
When the page sits behind a login, on staging, or on a site you don’t run, capture it first. start_capture_session opens a real browser window that you drive:
{ "tool": "start_capture_session", "args": { "url": "https://staging.example.com/dashboard" } }
It returns a sessionId immediately and does not wait. Log in, press Capture page in the Velloo toolbar on each page you need, then Done; the agent polls list_captures meanwhile rather than calling start_capture_session again. From a terminal, velloo capture https://staging.example.com/dashboard opens the same kind of session (velloo capture list browses stored captures, velloo capture delete <id> removes one).
get_capture { captureId } then returns the evidence: absolute files paths including page.png (the authoritative reference), a structural outline with each node’s rect and HIDDEN / OFFSCREEN markers, fonts, downloaded image assets, and themeCss, the page’s real custom properties. Pass that CSS to import_theme as css before composing. The session cookies never reach the agent, and captures live outside the design folder.
2. Rebuild the structure
Now compose the screen, the same compose-and-verify loop, with the page’s source open next to list_components.
A few porting-specific rules:
- Build into scanned screens. If
velloo initscanned the app’s routes, it already created one placeholder screen per route (id = route slug). Rebuild it in place withcomposeinmode: "replace", which swaps the placeholder tree for yours in one call;add_screenfor a scanned route returnsScreenIdConflict. - Keep Tailwind classes verbatim on a shadcn app. It shares Velloo’s component vocabulary, so most refs map one-to-one and the classes travel unchanged.
- Use the app’s own component before rebuilding it. The page you’re porting is built from components Velloo has probably already found: check
list_componentsfor the Repo shelves first, and compose the realFeatureCardrather than reassembling it from primitives. A repository component renders from your own installed copy and emits its exact import, which is the shortest path to a faithful port. Runpreview_statusonce before you start so the mount is known-good. - Then snippet, then extension. A presentational pattern you want to reuse and edit in one place becomes a snippet with typed params; snippets render for real. A dynamic leaf that can’t render as a tree of nodes — a chart wired to your data layer — becomes an extension registered with its real import path, optionally with live rendering so Velloo bundles your actual component.
- Replace
dark:variant patterns with semantic tokens. Velloo’s dark mode swaps token values, not a class, sodark:variants are inert on the canvas:bg-white dark:bg-backgroundbecomesbg-background.
3. Check fidelity against the live URL
compare_to_url renders your screen and captures the running app page at the same viewport, then pixel-diffs. What to diff against goes in source; the render size goes in viewport (default: the folder’s Desktop preset):
{
"tool": "compare_to_url",
"args": {
"screenId": "dashboard",
"source": { "url": "http://localhost:3000/dashboard" },
"viewport": { "w": 1440, "h": 900 }
}
}
The result carries similarity, changedRatio, heightDelta, and contentHeight, plus contentSimilarity when the heights differ. topMismatches ranks the worst diff regions and names the node responsible for each (so “the sidebar is off” arrives as a node reference, not a guess), and styleDiff, when both sides could be measured, lists the computed properties behind those regions (design versus page). A side-by-side PNG comes back too (URL on the left, Velloo on the right; pass image: false for metrics only).
Useful knobs:
mode: "dark"renders the Velloo side dark and best-effort drives the target page dark (prefers-color-scheme,.dark/data-themeon the html element,localStorage.theme), so dark fidelity checks against the app’s real dark theme.source.settleTimeoutMs(default 8000) caps the network-quiet wait before capture. Raise it for data-heavy pages that paint a spinner first.source.auth:storageStatePath(a Playwright storage-state JSON),cookies, orlocalStorageget you past a login wall to the real page.source.cache:freeze: truecaptures a dynamic page (feed, dashboard, per-user content) once and diffs every later call against that frozen reference, so drifting content doesn’t makesimilarityjitter.ttlMsbounds staleness (default five minutes);refresh: truere-samples after you change the app.
For a page you captured in a browser session, diff against the stored capture instead of the live URL. It is already past the login and frozen, so it can’t bounce to a login page or drift between calls; the viewport comes from the capture:
{ "tool": "compare_to_url", "args": { "screenId": "dashboard", "source": { "captureId": "<id from list_captures>" } } }
4. Iterate, then flip the source of truth
Fix what topMismatches points at, in order, re-compare, repeat. Calibration:
- 0.85+ similarity is a faithful structural port.
- Don’t chase 1.0. Fonts and imagery legitimately differ; the headless render substitutes some emoji glyphs and doesn’t fetch remote images, so a hotlinked avatar shows a fallback box. Use
upload_assetplus a localImageorPlaceholderfor art that needs to be pixel-faithful, and let the rest go.
Once the port is faithful, the screen is the design source going forward: redesign on the canvas, verify with screenshots, and push changes back with emit_code.