3.8 KiB
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
LintProcessorreceives{ videoId }from thelintqueue- Calls
LintingService.lintVideo(videoId) - Fetches video with config, template, existing lint results, and team's
disabledLintRules - Filters rules: removes any whose
codeis indisabledLintRules - Runs each active rule's
check()method - 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:
- Existing unresolved
LintResultrows for that rule are deleted Video.lintStatusis recomputed for all affected videos- 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
- Create
backend/src/modules/linting/rules/your-rule.rule.tsimplementingLintRule - Import and instantiate it in
LintingService.rules[]array - The rule will run automatically on all subsequent lint jobs
Related
- 03 - Linting API
- 01 - Video Management
- 13 - Team Settings