> ## Documentation Index
> Fetch the complete documentation index at: https://velt-codex-ui-customization-guide-refresh.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Build methodology: how to actually build a customization

> Follow the extract, map, build, and measure workflow for implementing Velt customizations from designs.

This is the **process**. The decision tree picks the *layer*; this page explains *how to build* the customization once that layer is chosen. Use it as a measured workflow: inspect the live rendered DOM, compare every mapped state against the design, and do not mark the surface done until every required element and behavior passes.

## 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:

<Steps>
  <Step title="Extract">
    Produce the `designSpec`: exact spacing, sizing, radius, typography, colours, and the design's exported icon SVGs ([`Extraction`](/ui-customization/extraction)). Numbers are read, never eyeballed.
  </Step>

  <Step title="Map">
    For each element, decide the real slot, prop, variant, or icon from the manifest ([`Manifest`](/ui-customization/reference/manifest)): **props-first** (structure a prop produces is never CSS) and **supply every `mustSupply` slot** (the design's SVG/text, never a Velt default).
  </Step>

  <Step title="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.
  </Step>

  <Step title="Measure">
    Diff rendered computed styles against the `designSpec`, per element and per property ([`Verifying a customization`](/ui-customization/verifying-a-customization)); "looks close" is a FAIL.
  </Step>
</Steps>

Step 1 below is the design overview that feeds extract+map; Step 2 is build; the measured loop closes it.

***

## Step 1: Design overview (read EVERY frame before building anything)

Go through the Figma **frame by frame** and write down, exhaustively:

1. **Which features/surfaces exist**: e.g. a comments **sidebar** AND a **pin dialog** are two different surfaces; don't miss one. List each.
2. **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.
3. **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**.
4. **Exact tokens**: colors (incl. mention/link color), type sizes/weights/line-height, spacing, radius, shadows, icon set.
5. **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`](/ui-customization/reference/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`](/ui-customization/reference/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).

The output is a complete, per-surface, per-state checklist plus the unsupported-items list. This is what the implementation and verification steps work against.

***

## 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 every `mustSupply` 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:

1. **Render** the surface in the live app (shadow off so class CSS reaches), with seeded data so the element actually shows.
2. **Inspect the LIVE rendered element** (not the hidden registry template: those `*-wireframe` tags are 0-size copies) to find its real Velt class (`.velt-thread-card--name/--time/--message`, `.s-user-avatar-container`, `.velt-composer--submit-button`, …).
3. **Apply the mapped `cssDecls` for that element** (the exact numbers from the `designSpec`) to that class, overriding Velt's default with `!important` (R9b). **Don't re-measure or eyeball: the numbers are already exact in the `designSpec`.**
4. **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.

**c. Interactivity & states.** Then handle hover/active states: e.g. resolve + kebab hidden by default, revealed on card hover; keep them visible while the options dropdown is open (the "active" case: hover OR a button under action). Fix empty-control gaps (`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:

```text theme={null}
for each block (composer-default first, then states):
   build/patch the block's elements  (Step 2 above: structure → supply slots → reconcile → exact numbers)
   SEED + DRIVE the block's state in-app  (block.drive/fixture: type/click/hover/resolve; wait for drive.assert)
   CAPTURE device-res evidence
   MEASURE:  visual diff with text masked  (chrome-only; any significant named region ⇒ FAIL)
          + computed-style comparison  (exact style Delta E below 2 / ±1px + layout box/gap/relation)
           + layer reconciliation (R22/R23) + behavior contract check (R25)
   iterate until: no significant visual region ∧ delta tables empty ∧ contract ok  → block PASS
   advance only on PASS
terminate only when the complete checklist review passes for ALL blocks
```

Why this is mechanical: text has a Figma-vs-Chrome glyph-rendering floor, so visual comparison should focus on chrome, missing elements, extra elements, and wrong structure. Computed-style comparison owns exact color, size, and position. Neither check is enough alone; both must be clean. Keep evidence collection separate from the verdict: collect the data first, then decide whether the block passed. A retry is only progress if the significant-region or failing-diff count drops. See the traps in [`Build gotchas`](/ui-customization/build-gotchas).
