# Lint & QA codes

> Every code comp.lint() and renderVideo({ qa }) can report, its severity, and what to do about it.

`comp.lint()` returns document findings before rendering. `renderVideo({ qa })` returns the same document findings plus measurements of the encoded frames and audio, in `result.qa.issues`. Each issue is `{ severity, code, message, layerId?, startSec?, endSec? }`. `qa.passed` is false if any **error** or **warning** is present.

## Document checks (lint and QA)

| Code | Severity | Means | Fix |
| --- | --- | --- | --- |
| `invalid_gradient` | error | a `background`/`fill` gradient doesn't parse; draws nothing | check the CSS syntax, ≤ 8 stops |
| `unsupported_image_format` | error | `Layer.image` given `.json`/`.lottie`, video or audio | use `Layer.lottie` / `Layer.video` / `Layer.audio` |
| `unknown_color` | warning | a color string that isn't a color; draws transparent | use hex, `rgb()`, `hsl()`, `oklch()` or a CSS name |
| `low_contrast_text` | warning | text below 3:1 against the solid color behind it | use the suggested color, or `Color.readableOn(bg)` |
| `offscreen_layer` | warning | a static absolute layer entirely outside the canvas | fix `x`/`y` or remove it |
| `animation_truncated` | warning | keyframes past the layer's `durationFrames` never play | extend the layer or move the keys |
| `shadow_unsupported_light` | warning | `castShadow` on a point or ambient light | use a directional or spot light |
| `pbr_without_environment` | info | `roughness`/`metallic` set without `Layer.environment()` | add an environment or a `Lighting` rig |
| `multiple_shadow_lights` | info | several `castShadow` lights; only the first casts | keep one |
| `static_track` | info | every keyframe on a track holds the same value | remove the track or fix the values |

## Render measurements (QA only)

| Code | Severity | Means |
| --- | --- | --- |
| `frames_missing` | error | fewer frames encoded than the composition has |
| `black_frames` | warning | ≥ `minBlackSec` (0.5 s) of black; has `startSec`/`endSec` |
| `audio_clipping` | warning | samples at or beyond full scale |
| `loudness_off_target` | warning | integrated loudness more than `lufsTolerance` (1 LU) from `targetLufs` |
| `frozen_video` | info | picture unchanged ≥ `minFrozenSec` (2 s); fine for a held title |
| `audio_silence` | info | ≥ `minSilenceSec` (2 s) below `silenceDbfs` (−60) |
| `audio_silent` | info | the whole track is silent |

## Use in CI or an agent loop

```ts
const result = await comp.renderVideo({ outputPath: "output/film.mp4", qa: { targetLufs: -14 } });
const blocking = result.qa!.issues.filter((i) => i.severity !== "info");
for (const i of blocking) console.error(`${i.code}${i.layerId ? ` [${i.layerId}]` : ""}: ${i.message}`);
process.exit(result.qa!.passed ? 0 : 1);
```
