The one principle
Build in small patches, each made pixel-perfect before the next. Never build the whole design in one pass. A run that registers every wireframe + writes all the CSS at once and then “judges the surface at 90%” is the failure mode. Instead: one element → match it to the pixel → next element. Small, verified, compounding.The pipeline: extract → map → build → measure
Fidelity is a data problem, not a judgment one. Don’t look-and-style; run this pipeline:Extract
Produce the
designSpec: exact spacing, sizing, radius, typography, colours, and the design’s exported icon SVGs (Extraction). Numbers are read, never eyeballed.Map
For each element, decide the real slot, prop, variant, or icon from the manifest (
Manifest): props-first (structure a prop produces is never CSS) and supply every mustSupply slot (the design’s SVG/text, never a Velt default).Build
Execute that map: wireframe files, an
icons/ file from the exported SVGs, host props set, and the designSpec’s exact cssDecls applied to the real classes.Measure
Diff rendered computed styles against the
designSpec, per element and per property (Verifying a customization); “looks close” is a FAIL.Step 1: Design overview (read EVERY frame before building anything)
Go through the Figma frame by frame and write down, exhaustively:- Which features/surfaces exist: e.g. a comments sidebar AND a pin dialog are two different surfaces; don’t miss one. List each.
- Per surface, every element: for a dialog: header (or none), thread card (avatar, name, time, message, mentions, reply/toggle), composer, etc. For a sidebar: header (title + filter), composer, list.
- Every state shown: collapsed-by-default vs expanded, empty placeholder, composer focused, composer expanded (long message → grows to a max-height then scrolls), autocomplete/@mention dropdown, hover (reveals resolve + kebab), selected, resolved (greyed + unresolve), filter dropdown open, options dropdown, toasts/tooltips, selected-filter tick. Note which state the user wants as the default.
- Exact tokens: colors (incl. mention/link color), type sizes/weights/line-height, spacing, radius, shadows, icon set.
- What is NOT supported by default: explicitly list every piece the SDK can’t do out of the box, and the plan for each:
- achievable via a prop/config/wireframe slot → note which,
- needs primitives/headless + custom logic → confirm the data model actually supports it (
Data models) before claiming feasible; if a field/event doesn’t exist, it’s a real gap → document it + suggest the closest supported alternative (e.g. a “mentions” filter isn’t in the minimal filter → suggest assigned-to-me / involved), - needs an event-driven addition (e.g. a resolved toast, a link-copied tooltip) → clean; subscribe to the event (
Events), - might feel hacky (e.g. a “Cancel” button the SDK has no slot for → clear the composer via JS) → document the approach and confirm with the user before shipping it: never ship a brittle hack silently (R0).
Step 2: Implementation (one small patch at a time, to pixel-perfection)
Pick one surface (dialog or sidebar: dialog first is usually easiest). Then, within it: a. Build the structure, slot by slot. Declare the wireframe tree for that surface (full container trees: containers drop undeclared children). Get it rendering before styling. a2. Supply every slot + set props first. Before styling, fill everymustSupply slot from your implementation map (the design’s exported SVG icon, the exact label, the explicit menu items: never a Velt default), and set the host props that produce structure. Only set props that a design cue justified (R24). Don’t add a feature flag the design doesn’t show (visibilityOptions adds an unwanted banner). Structure from a prop is never built in CSS.
a3. Reconcile layers: paint once on the box-matched owner (R22/R23). A Figma node is one rectangle; the DOM is a nested stack. Before per-element styling, inspect each painted node in the live rendered output and apply the design’s box-painting (background, border, radius, padding) to the owner, meaning the layer whose box matches the design node: your .hw-* element or the leaf Velt class. For every co-box wrapper that duplicates painting, reset only the offending box-painting property (padding:0, background:transparent, and similar, with !important). Never reset functional CSS such as flex, overflow, or position. This flattens redundant Velt wrappers (velt-sidebar-container, app-comment-sidebar-list, the page-mode-composer dialog, and similar) so the design’s gutter, background, and border land once. Then compose each group from the layout relationships: header row = [Avatar | Name·Time·Edited·Unread | actions], message under the header at the same left edge, actions top-right hover-reveal. Derive inner spacing from the designSpec gaps, never from stacked padding.
b. Style element by element: apply the EXACT numbers (this is the core). Style the slot’s correct role (R23): for a menu/popup apply the chrome to the content slot, never the dropdown container (it wraps the trigger → a 210px box) or the trigger (style that as the 24px icon); scope mention CSS to the message’s mentionScope only. For each element, in a small patch:
- Render the surface in the live app (shadow off so class CSS reaches), with seeded data so the element actually shows.
- Inspect the LIVE rendered element (not the hidden registry template: those
*-wireframetags are 0-size copies) to find its real Velt class (.velt-thread-card--name/--time/--message,.s-user-avatar-container,.velt-composer--submit-button, …). - Apply the mapped
cssDeclsfor that element (the exact numbers from thedesignSpec) to that class, overriding Velt’s default with!important(R9b). Don’t re-measure or eyeball: the numbers are already exact in thedesignSpec. - Measure the rendered computed style against the
designSpec(Delta E below 2, ±1px) and record a delta table; close any failing rows. Then move to the next element.
display:none on an empty unresolve button). Composer collapsed→expanded→max-height→scroll. Hidden scrollbars (scrollbar-width:none).
d. Test every feature functionally, as a real user. Edit, delete, copy link, add reply, delete main/reply, resolve/unresolve, draft, edited badge, tagging one/many users. Keep fixing until it’s 100% right functionally and visually. (Draft/edited badges may not be in the design: keep them, themed to match.)
e. Next surface. Repeat a-d for the sidebar (header title + filter, page-mode composer, list).
f. The unsupported items, last. Implement the event-driven ones (toast, tooltip) cleanly; confirm the hacky ones with the user; document the genuine gaps with their suggested alternatives.
The loop is block by block
Every Figma frame or state is one block. The design’s frames are the checklist, so “stopped at the happy path” is incomplete when another required state exists. Finish each block to a true match before starting the next:Build gotchas.
