Files
youtube-studio-flow/documentation/YouTube Studio Flow/02 - Features/03 - Metadata Linting.md
T

66 lines
3.8 KiB
Markdown

# Metadata Linting
## User Perspective
The linting feature automatically checks video metadata and descriptions for quality issues. Lint results appear as badges on the video list and as a collapsible section in the video editor header.
### Lint Page (`/linting`)
Shows all open (unresolved) lint results across the team. Filter by severity or rule code. Results can be individually resolved (marking them as acknowledged). Navigating to the linting page automatically triggers a background heal that corrects any stale `lintStatus` values on video records.
### Lint Status on Videos
Videos show one of three statuses:
- **OK** — no open lint issues
- **WARNING** — at least one warning, no errors
- **ERROR** — at least one error
The "Lint Issues" tab on the Videos page shows all videos with WARNING or ERROR status.
### Resolving Issues
Resolving a lint result marks it as `resolvedAt = now()`. It will reappear on the next lint run if the underlying issue is not fixed. Bulk-resolve is supported.
## Developer Perspective
### Rules
All rules implement the `LintRule` interface: `{ code: string, severity: LintSeverity, check(video): LintIssue | null }`.
`LintSeverity` has three values: `ERROR`, `WARNING`, and `INFO`. No current rule uses `INFO` — it is reserved for future informational hints. **Important:** `computeStatus()` only checks for `ERROR` and `WARNING` when computing `Video.lintStatus`. A video with only `INFO` results will have `lintStatus: OK`. This is intentional — INFO is non-actionable and should not surface as a problem on the video list.
| Rule Code | Severity | What it checks |
|---|---|---|
| `TITLE_WEAK` | WARNING | Title < 20 chars or contains generic words (video, test, untitled, new video, upload) |
| `TITLE_TOO_LONG` | WARNING | Title > 100 characters |
| `DESC_MISSING_CTA` | WARNING | Description lacks CTA keywords (subscribe, abonnieren, follow, like, comment, cta) |
| `DESC_MISSING_CHAPTERS` | WARNING | Description has fewer than 2 timestamp patterns (`\d{1,2}:\d{2}`) |
| `DESC_EMPTY_PLACEHOLDER` | ERROR | Description contains unresolved `{placeholder}` patterns |
| `DESC_DUPLICATE_HASHTAG` | WARNING | Description has duplicate hashtags (case-insensitive) |
| `DESC_REQUIRED_LINK_MISSING` | ERROR | Description missing a required link defined in `template.rules.requiredLinks` |
| `DESC_OUTDATED_SPONSOR_COPY` | ERROR | A CAMPAIGN block references an expired or inactive campaign |
| `REMOTE_CONFLICT` | ERROR | `video.remoteConflict` is true — YouTube-side metadata changed since last sync |
### Lint Execution Flow
1. `LintProcessor` receives `{ videoId }` from the `lint` queue
2. Calls `LintingService.lintVideo(videoId)`
3. Fetches video with config, template, existing lint results, and team's `disabledLintRules`
4. Filters rules: removes any whose `code` is in `disabledLintRules`
5. Runs each active rule's `check()` method
6. Wraps in a transaction: deletes all unresolved results, creates new ones, updates `Video.lintStatus`
### Disabling Rules
Team admins can disable specific rules in Settings. When a rule is disabled:
1. Existing unresolved `LintResult` rows for that rule are deleted
2. `Video.lintStatus` is recomputed for all affected videos
3. Future lint runs skip that rule
### Stale lintStatus
`Video.lintStatus` is a denormalized cache. It can become stale if `LintResult` rows are deleted outside of `lintVideo()`. The heal endpoint `POST /lint/team/recompute-status` corrects all stale statuses in one query. This endpoint is called automatically when the linting page loads.
### Adding a New Lint Rule
1. Create `backend/src/modules/linting/rules/your-rule.rule.ts` implementing `LintRule`
2. Import and instantiate it in `LintingService.rules[]` array
3. The rule will run automatically on all subsequent lint jobs
## Related
- [[03 - Linting API]]
- [[01 - Video Management]]
- [[13 - Team Settings]]