> ## 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 gotchas: common traps and fixes

> Avoid common build-time mistakes when translating design specs into Velt UI customizations.

These are common failure modes when building Velt UI customizations from a design. Check them before assuming the SDK is broken.

## Inspecting

* **Inspect the LIVE rendered node, not the registry template.** A wireframe is cloned: the `velt-*-wireframe` custom-element tags are the hidden registry copy (0-size, empty). `document.querySelector('.hw-x')` may hit that copy. Always pick the element with `getBoundingClientRect().width > 0` (the visible clone) and read *its* classes/computed styles. Measuring the wrong node is how you "verify" something that's actually broken.

## Wireframe clone behavior

* **Some slots OVERWRITE their inner markup with their own label.** `ToggleReply` replaced custom `<svg/> + <span>Reply</span>` with a plain "Reply" text node; `CopyLink` did the same. **Fix:** don't nest icons in those slots: inject the icon via CSS `::before` (a data-URI SVG on your `.hw-*` class), which survives the clone.
* **Wireframe MARKUP changes need a FULL page reload to take effect, not just new wireframes.** CSS edits hot-reload fine, but the `<VeltWireframe>` registry is built at mount: changing an existing template's markup (e.g. switching the composer's send to a self-closing `ActionButton`, or swapping Cancel to a `VeltButtonWireframe`) **re-renders under Fast Refresh but does NOT re-register the template**: the browser keeps rendering the OLD wireframe, so your fix "doesn't work" until a hard reload. After ANY `*Wf.tsx` change: hard-reload (Cmd-Shift-R), re-auth, reopen, and verify in a freshly-loaded tab, never a hot-reloaded one. (This masked a *correct* composer-submit fix as "still broken".)
* **Container slots drop undeclared children**: declare the full child tree you intend to use.

## Styling / scoping

* **Class CSS needs shadow off + `!important`** (R6/R9b). With `shadowDom={false}` the live classes are reachable; Velt's runtime CSS is high-specificity, so overrides need `!important`.
* **The page-mode composer renders the WHOLE dialog wireframe** (your `.hw-card` and all). It inherits the card chrome (border/shadow/resolve-icon) and crushes the input. **Fix:** scope the card chrome off in that context: `.velt-comment-dialog--page-mode-composer .hw-card { border:none; box-shadow:none; padding:0; background:transparent }`: leaving just the composer pill. (Alternatively give the page-mode composer its own variant via `pageModeComposerVariant`.)
* **Composer "active" state = the `.velt-composer-open` ancestor class** (focus/compose), not a "has-text" class (there is none). Style the send button: grey/disabled by default, dark/enabled under `.velt-composer-open`.
* **Avatar fill color is user-data-driven**, not CSS: "User 1" renders peach. To match a design that shows a flat dark avatar, override `.…s-user-avatar-initial-container { background }` + the initial color (this overrides per-user colors: a deliberate choice to flag).
* **The send-arrow indigo lives on an inner element** (`.velt-composer--input-button`), not the outer `.velt-composer--submit-button`: override the inner one.

## Composer actions · collapsed replies · resolved state (design patterns that bit us)

* **Cancel button: use `VeltButtonWireframe`, never a raw `<button>`.** The dialog/reply composer has no native Cancel slot. Render Cancel as a `VeltButtonWireframe` (a Velt-owned button) and wire it in the host via `useVeltEventCallback("veltButtonClick")` to clear and collapse the composer. A raw `<button onClick>` in wireframe markup does not run because it is cloned to plain DOM (R4), and it breaks specifically in the **in-thread reply composer**. Page mode can look fine while the reply composer is dead. The host handler must scope to both composers, but **`commentAnnotation` does not distinguish them because both carry one**. The real distinguisher is the annotation's comment count: the **page-mode** composer fires with a fresh **draft** (`commentAnnotation.comments.length === 0`), while a **reply** composer fires with the **existing thread** (`comments.length > 0`). Route the clear to the page-mode composer in the draft case, otherwise to the selected dialog's reply composer. Also key the Send button's enabled/dark state off the submit button's `:disabled` attribute rather than only `.velt-composer-open`; it tracks empty-vs-filled exactly.
* **Send button → leave `Composer.ActionButton` SELF-CLOSING; paint the arrow with CSS.** Injecting a child (`<svg>`/`<span>`) into `ActionButton` is dropped by the clone and can kill the native submit in the reply composer. Leave it empty so Velt renders the functional submit, then mask the up-arrow via CSS (`::after`/`mask` on the live button class: grey idle → dark under `.velt-composer-open`).
* **"Show N replies…" (MoreReply): the clone DROPS trailing text, so add the ellipsis in CSS.** Velt renders the text in `.velt-hidden-count`; the cloner drops every node after the last velt element, so the trailing `…` is gone: re-add it via `::after { content:'…' }` on `velt-comment-dialog-more-reply-text-internal`. Gate the row on `:has(.velt-hidden-count)` (NOT `:not(:empty)`) so a fully-expanded thread (empty MoreReply) doesn't show a lone chevron. Indent to the message column and draw the **rail-line continuation** (a 1px line in the avatar gutter joining the chevron to the next avatar); suppress Velt's default full-width divider. **Also collapse the EMPTY slots**: a single comment / fully-expanded thread renders an empty `velt-comment-dialog-more-reply-internal` AND empty `velt-comment-dialog-toggle-reply-internal` that still take vertical space *inside the card border* → dead space below "Reply". Add `velt-comment-dialog-more-reply-internal:not(:has(.velt-hidden-count)), velt-comment-dialog-toggle-reply-internal:not(:has(…count…:not(:empty))) { display:none !important }` so only a populated "Show N replies" row occupies space (the user-reported extra-spacing, on open AND resolved cards).
* **Resolved card = muted AND no reply.** Per the design a resolved comment hides its reply affordance. Velt adds no `--resolved` class: detect resolved via the rendered unresolve button (`.velt-comment-dialog:has(velt-comment-dialog-unresolve-button-internal .hw-icon-btn)`), then mute colours/avatar AND `display:none` the reply + `velt-comment-dialog-toggle-reply-internal` + any reply composer. Leaving them shows empty bottom space.

## Dropdowns / selected state

* **The minimal-filter `SelectedIcon` slot renders as ONE standalone, full-width, `opacity:0` element**: it does NOT auto-place a tick per row. **Fix:** hide that slot and put a ✓ on the actually-selected row via CSS: `.…content-item--selected .hw-filter-item::after { content:"✓"; margin-left:auto }`. (Gating with `VeltIf {isSelected}` does NOT work in this V1 sort/filter context: it resolves falsy.)
* **A right-edge-anchored dropdown can overflow the viewport** (the trigger sits at the sidebar's right edge). The content's default `left/right` may push a wide menu off-screen, clipping the tick. **Fix:** shift the menu into view (e.g. `transform: translateX(-Npx)` on the menu, or right-anchor it) so it opens leftward.

## Interaction driving (when verifying in the browser)

* **Velt triggers need a real pointer click; `element.click()` (JS) often won't fire the Angular handler.** Use a real click at the element's coordinates.
* **Velt auth or `documentsReady` can stall after reloads** (`useCurrentUser` doesn't emit, so nothing mounts and the `velt-*` count is 0). This is an environment block, not a build failure. Wait longer for the mount, recover with a fresh tab, or re-authenticate in the app. Triage app vs. build before blaming the customization.
