Rules & Checks
This area covers 44 features. Watch the walkthrough, then use the reference below — each feature links to the exact moment it appears (▶).
Who this is for: Project lead / reviewer. Each feature below lists the role/permission it requires.
Features
Create custom translation rule ▶ 00:06
As a translator lead, I want to create custom regex-based rules to enforce translation patterns, so that the team maintains consistent quality across translations.
How it works. User opens Rules page, clicks '+ Add Rule' (RuleCreateDialog or RuleEditor), fills rule name (required), description (optional), selects check type (source-target-match / source-requires-target / target-forbids), enters regex patterns, sets severity (major/minor) and enabled status. Live preview shows matching cells from current file. Can add optional autofix with find/replace pattern, flags, and before/after preview. Clicking 'Create rule' saves it to project rules list.
Key files
src/components/RuleCreateDialog.tsx:1-220src/components/RuleEditor.tsx:117-504src/components/RulesSurface.tsx:201-210
Edit existing custom rule ▶ 00:00
As a translator lead, I want to modify existing rules without recreating them, so that I can refine patterns based on new findings.
How it works. User navigates to Rules page, clicks pencil icon on a rule row (RulesSurface), inline RuleEditor appears pre-populated with existing rule data. User can edit name, description, check type, patterns, severity, enabled status, and autofix. Changes are live-previewed. Clicking 'Save changes' updates rule; 'Cancel' closes editor.
Key files
src/components/RuleEditor.tsx:117-504src/components/RulesSurface.tsx:455-497
Delete custom rule ▶ 00:00
As a project maintainer, I want to remove rules that are no longer needed, so that I can keep the rule set clean and relevant.
How it works. User opens Rules page, clicks trash icon on rule row. ConfirmActionDialog appears with title 'Delete rule', warning text mentioning irreversibility and scope (everyone in project), checkbox to confirm understanding, 'Delete rule' button. After checking the box, clicking button deletes rule and removes it from list.
Key files
src/components/RulesPage.tsx:244-275src/components/RulesSurface.tsx:476-478src/components/ConfirmActionDialog.tsx
Enable/disable custom rule ▶ 00:00
As a translator, I want to temporarily disable rules without deleting them, so that I can evaluate their impact or silence false positives.
How it works. User navigates to Rules page, locates a rule row. Toggle switch on the right labeled 'Enabled' shows rule's current enabled status. Clicking toggle immediately updates rule's enabled flag. Disabled rules do not fire infractions. Audible/visual feedback indicates change.
Key files
src/components/RulesPage.tsx:240-243src/components/RulesSurface.tsx:467-475
View built-in checks ▶ 00:00
As a translator, I want to see the built-in quality checks available, so that I understand what patterns are automatically being checked.
How it works. User navigates to Rules page. BuiltinChecksList card renders below header, showing ~9 built-in checks (empty-target, target-equals-source, placeholder-integrity, number-integrity, end-punctuation-mismatch, double-space, repeated-word, unpaired-symbols, abbreviation-mismatch). Each row shows check name, description, violation count (if any), severity dropdown, enable toggle. Checks are code-defined and immutable per check.
Key files
src/components/BuiltinChecksList.tsx:35-126src/lib/lqa/builtin-registry.ts:24-110
Enable/disable built-in check ▶ 00:00
As a translator, I want to toggle built-in checks on/off, so that I can silence checks that are not relevant to my translation context.
How it works. User navigates to Rules page, BuiltinChecksList displays. For each built-in check, a toggle switch labeled 'Enabled' indicates current status. Clicking toggle calls setBuiltinOverride(checkId, { enabled: checked, severity }), persisting override to project settings. Disabled checks no longer fire violations.
Key files
src/components/BuiltinChecksList.tsx:111-119src/hooks/useRules.ts
Change built-in check severity ▶ 00:00
As a project lead, I want to adjust the severity of built-in checks, so that I can prioritize which violations are critical vs. style preferences.
How it works. User navigates to Rules page, BuiltinChecksList displays. Each built-in check has a Severity dropdown (Major/Minor). User clicks dropdown, selects severity level. Selection calls setBuiltinOverride(checkId, { enabled, severity: newSeverity }). Severity badge on rule row updates color (red for major, amber for minor).
Key files
src/components/BuiltinChecksList.tsx:90-110src/lib/lqa/builtin-registry.ts:16
Suggest rules from validated translations ▶ 00:06
As a translator lead, I want the system to suggest rules based on my validated translations, so that I can discover patterns I should formalize into rules.
How it works. User navigates to Rules page, clicks 'Suggest from edits' button (RuleSuggestDialog). Dialog prompts to analyze validated pairs. User clicks 'Analyze my validated edits'. System extracts validated source→target pairs from project, sends to LLM with rule-suggestion prompt, receives 1-5 RuleSuggestion objects. Review stage shows each suggestion with name, description, severity, check pattern. User toggles suggestions on/off (checkbox), clicks 'Add N rules' to commit accepted suggestions.
Key files
src/components/RuleSuggestDialog.tsx:43-265src/lib/rules/rule-suggester.ts:76-100e2e/JOURNEYS.md
Suggest rules from document import ▶ 00:00
As a translation manager, I want to import rules from a style guide or glossary document, so that I can quickly establish rules from existing brand/style guidelines.
How it works. User navigates to Rules page, clicks 'Import from doc' button (RuleImportDialog). Dialog shows drop zone for files (.txt, .md, .pdf, .docx up to size limits) or paste zone. User drops/pastes document. System runs two-pass extraction: (1) extract candidate rules from text, (2) structure into JSON RuleSuggestion objects. Review stage shows extracted rules; user toggles and commits accepted ones.
Key files
src/components/RuleImportDialog.tsx:64-365src/lib/rules/rule-extractor.tssrc/lib/frontier/parse-document.ts
Test rule against sample source/target ▶ 00:00
As a rule author, I want to test my rule before saving, so that I can verify it matches the intended pattern.
How it works. User opens rule creation dialog (RuleCreateDialog), enters Source and Target test inputs, clicks 'Test' button. System evaluates rule logic against inputs, displays pass/fail message (e.g. '✓ Pass — pattern found in both' or '✗ Target contains forbidden pattern'). Results update in real-time as user tweaks pattern.
Key files
src/components/RuleCreateDialog.tsx:49-87src/components/RuleCreateDialog.tsx:199-212
Live preview of rule matches in current file ▶ 00:00
As a rule author, I want to see example cells matching my rule, so that I can verify the pattern is not over-firing.
How it works. User creates/edits rule in RuleEditor. As they type pattern, live preview section updates (debounced 300ms). Shows count of matching cells in current file, plus up to 3 sample cells with source and target truncated. Preview only shows for non-empty, valid patterns. Empty pattern hides preview.
Key files
src/components/RuleEditor.tsx:83-105src/components/RuleEditor.tsx:395-420
Autofix: add regex-based find/replace to rule ▶ 00:00
As a translator lead, I want to attach automated fixes to rules, so that violations can be auto-corrected without manual review.
How it works. User creates/edits rule in RuleEditor, scrolls to 'Add autofix (optional)' toggle. Clicking toggle reveals regex-replace form with Find pattern, Replace with (can use $1 backrefs), Flags (e.g. 'gi') inputs, and sample text preview. User enters pattern, replacement, flags, optionally types sample text to see before/after transformation. Clicking 'Save changes/Create rule' persists autofix. Autofix badge appears on rule row.
Key files
src/components/RuleEditor.tsx:156-159src/components/RuleEditor.tsx:422-487src/components/RulesPage.tsx:280-301
View rule details in drawer ▶ 00:00
As a translator, I want to see which cells are breaking a rule and which are following it, so that I can understand the rule's impact.
How it works. User opens editor, clicks 'Try to fix all' on a rule row or rule is loaded via ?openRule= URL param. RuleDrawer opens on right side of screen showing rule name, severity icon, saved autofix info (or 'No saved fix yet'). Drawer lists breaking cells (red-highlighted, ~20 max) and passing cells (green-highlighted, ~10 max). User can click a cell to navigate to it in the editor.
Key files
src/components/RuleDrawer.tsx:19-123src/components/RulesPage.tsx:232-235
Amend rule from drawer ▶ 00:00
As a translator lead, I want to quickly jump to editing a rule's autofix, so that I can fix violations without navigating away.
How it works. User opens RuleDrawer (via 'Try to fix all'), clicks 'Amend rule' button. Navigation occurs to /project/{id}/rules?ruleId={ruleId}&focus=autofix. User lands on Rules page with rule row expanded, autofix section visible and scrolled into view.
Key files
src/components/RuleDrawer.tsx:38-40src/components/RulesPage.tsx:44-53
Rule mode selector (forbidden/required/match) ▶ 00:00
As a rule author, I want to choose between different rule types, so that I can express the exact constraint I'm trying to enforce.
How it works. User creates/edits rule in RuleEditor. Mode selector buttons show three options: 'Forbidden' (target must not contain pattern), 'Required' (if source matches, target must match), 'Must match' (pattern in both). User clicks a mode. UI updates to show/hide relevant inputs. Sentence at top of form updates to explain the selected mode.
Key files
src/components/RuleEditor.tsx:132-139src/components/RuleEditor.tsx:264-306src/components/RuleEditor.tsx:74-79
Rule regex vs. literal toggle ▶ 00:00
As a translator without regex knowledge, I want a 'literal text' mode, so that I can create rules without learning regex syntax.
How it works. User creates/edits rule in RuleEditor, sees pattern input with placeholder hint (\d+ for regex, 'exact text' for literal). Below input, button 'Switch to literal' or 'Switch to regex'. Clicking toggle switches mode. In literal mode, user's input is automatically escaped before use (via escapeRegex()). Mode is preserved per-field. Pattern validation ignores errors in literal mode.
Key files
src/components/RuleEditor.tsx:140src/components/RuleEditor.tsx:369-391src/components/RuleEditor.tsx:28-30
Rule severity picker (major/minor) ▶ 00:00
As a project lead, I want to categorize violations by severity, so that translators know which rules are critical vs. style guidelines.
How it works. User creates/edits rule in RuleEditor. Severity buttons show 'Major' (red background) and 'Minor' (amber background). User clicks to select. Selected button highlights. Severity is saved with rule. On RulesPage/RulesSurface, rule row shows severity badge and icon color-coded to severity.
Key files
src/components/RuleEditor.tsx:121src/components/RuleEditor.tsx:309-328
Rule enabled checkbox ▶ 00:00
As a translator, I want a clear visual indicator of rule enabled status, so that I know which rules are active.
How it works. User creates/edits rule in RuleEditor. Checkbox labeled 'Enabled' is checked by default. Clicking unchecks. Save/Create persists enabled status. On RulesPage/RulesSurface, each rule row shows Enabled toggle switch.
Key files
src/components/RuleEditor.tsx:122src/components/RuleEditor.tsx:330-339
Rule row expand/collapse autofix editor ▶ 00:00
As a translator, I want to edit autofix details inline without navigating away, so that I can refine fixes without context switching.
How it works. User navigates to Rules page (RulesPage). Each rule row shows chevron (down/up). Clicking chevron expands row, revealing AutofixEditor section with existing autofix pattern/replacement/flags inputs or empty if no autofix. Clicking again collapses. Autofix can be edited, saved, or cleared from expanded section.
Key files
src/components/RulesPage.tsx:236-251src/components/RulesPage.tsx:280-301
Rule list filtering/search ▶ 00:00
As a translator, I want to search for rules by name, so that I can quickly find the rule I need to work with.
How it works. User navigates to Rules page. Search input is present (if implemented) to filter rules by name. Typing filters rule list in real-time. Only rules matching search text are shown.
Key files
src/components/RulesSurface.tsx (rules list + filter)src/hooks/useRules.ts
Promote project rule to org scope ▶ 00:00
As an org maintainer, I want to promote successful project rules to org-wide scope, so that other projects can benefit from proven patterns.
How it works. User (with canEditOrgRules=true) navigates to Rules page (RulesSurface), locates a project rule row, clicks 'Promote to org' button. Dialog opens: 'Promote rule to org?' with explanation that a copy will be added to org library, project copy is kept. User clicks 'Promote to org'. Rule is copied, id regenerated, scope='org', sourceProjectId set, added to org rules list.
Key files
src/components/RulesSurface.tsx:120-134src/components/RulesSurface.tsx:419-428src/components/RulesSurface.tsx:362-380
Request rule promotion (project_lead) ▶ 00:00
As a project_lead translator, I want to request promotion of a rule to org scope without editing privileges, so that org maintainers can review and approve.
How it works. User (with canRequestPromotion=true, e.g. org project_lead but not maintainer) navigates to Rules page (RulesSurface), locates a project rule row, clicks 'Request promotion' button. Async call to requestPromotion() is made. On success, button becomes 'Requested' badge with Clock icon. On duplicate, shows 'Already requested'. On error, shows 'Failed — try again'.
Key files
src/components/RulesSurface.tsx:136-150src/components/RulesSurface.tsx:430-454
Approve/dismiss rule promotion requests ▶ 00:00
As an org maintainer, I want to review and approve rule promotion requests from project leads, so that I can control which project-specific rules become org-wide standards.
How it works. User (with canEditOrgRules=true) navigates to Rules page (RulesSurface). In org rules section, 'Pending requests (N)' appears showing queued promotion requests. Each request shows rule name, description, requester name, and 'Approve' and 'Dismiss' buttons. Clicking 'Approve' creates org rule (new UUID, scope='org', sourceProjectId) and removes request. Clicking 'Dismiss' removes request without creating rule.
Key files
src/components/RulesSurface.tsx:170-188src/components/RulesSurface.tsx:324-357
Create org-wide rule ▶ 00:00
As an org maintainer, I want to create rules that apply to all projects in the org, so that I can enforce org-wide standards.
How it works. User (with canEditOrgRules=true) navigates to Rules page (RulesSurface). In Org Rules section, clicks '+ Add Org Rule' button. Inline RuleEditor appears. User fills in rule details (name, description, check type, patterns, severity, enabled). Clicking 'Create rule' saves rule with scope='org', assigns new UUID, and adds to org rules list.
Key files
src/components/RulesSurface.tsx:164-168src/components/RulesSurface.tsx:231-241src/components/RulesSurface.tsx:251-261
Edit org-wide rule ▶ 00:00
As an org maintainer, I want to modify org-wide rules, so that I can refine org standards without recreating rules.
How it works. User (with canEditOrgRules=true) navigates to Rules page (RulesSurface). In Org Rules section, locates rule, clicks pencil icon. Inline RuleEditor appears pre-populated with rule data. User edits name, description, check, patterns, severity, enabled status. Clicking 'Save changes' persists updates; 'Cancel' closes editor.
Key files
src/components/RulesSurface.tsx:152-156src/components/RulesSurface.tsx:279-318
Enable/disable org-wide rule ▶ 00:00
As an org maintainer, I want to toggle org rules on/off, so that I can pause rules across all projects without deleting them.
How it works. User (with canEditOrgRules=true) navigates to Rules page (RulesSurface). In Org Rules section, each rule row has toggle switch 'Enabled'. Clicking toggle calls updateOrgRule(ruleId, { enabled: checked }), persisting to org settings. Disabled org rules do not fire in any project.
Key files
src/components/RulesSurface.tsx:290-298
Delete org-wide rule ▶ 00:00
As an org maintainer, I want to remove org rules that are no longer needed, so that I can keep org standards current.
How it works. User (with canEditOrgRules=true) navigates to Rules page (RulesSurface). In Org Rules section, locates rule, clicks trash icon. Rule is immediately removed from org rules list and persisted.
Key files
src/components/RulesSurface.tsx:158-162src/components/RulesSurface.tsx:299-301
Harmonize violations (batch autofix with review) ▶ 00:00
As a translator lead, I want to fix multiple violations for a built-in check at once, so that I can quickly resolve systematic quality issues across the project.
How it works. User navigates to Rules page (RulesPage). For a built-in check with violations, clicks 'Harmonize all (N)' button. FixReviewPanel opens on right side showing rule, proposal kind, and previews. If rule has autofix, shows regex-based fixes with before/after. User can select/deselect cells to fix, must type rule name to confirm (FRO-186), clicks 'Apply X selected'. All changes applied (TODO: actual event dispatch for Y.Doc edits).
Key files
src/components/RulesPage.tsx:80-109src/components/RulesPage.tsx:152-174src/components/BuiltinChecksList.tsx:73-89src/components/FixReviewPanel.tsx:25-134
Rule infraction detection ▶ 00:00
As a translator, I want to see which cells violate which rules, so that I can prioritize corrections.
How it works. System continuously evaluates all enabled rules against all project cells. For each cell, checkRulesForCell() runs all enabled rules (custom + builtin + org rules) and returns array of RuleInfraction objects. Each infraction includes ruleId, cellId, fileId, message, and spans array (position + matched text). Infractions are displayed in editor, drawer, and rules page.
Key files
src/lib/rules/rule-engine.ts:44-154src/components/RulesPage.tsx:119-131src/lib/rules/rule-engine.ts:18-34
Waive rule violation ▶ 00:00
As a translator, I want to mark violations as acknowledged/acceptable for specific reasons, so that I can suppress false positives or exceptions without changing the rule.
How it works. User opens editor, sees violation in drawer or inline. Clicks 'Waive' or similar button. Modal or dialog appears asking for waive reason (optional). User confirms. Violation is marked as waived (added to project.waivers array). Waived violations are hidden from main view but may appear grayed out or in separate section.
Key files
src/lib/rules/waivers.ts:9-40src/lib/rules/waivers.ts:28-40
Unwaive rule violation ▶ 00:00
As a translator, I want to un-waive a violation, so that it becomes active again.
How it works. User navigates to rule details or waived violations view. Clicks 'Unwaive' or equivalent. Violation is removed from project.waivers, becomes active again.
Key files
src/lib/rules/waivers.ts:23-26
Autofix: batch regex-based fixing ▶ 00:00
As a translator lead, I want to fix all violations of a rule using a regex pattern, so that systematic errors are corrected automatically.
How it works. User opens editor with rule visible, clicks 'Try to fix all'. If rule has no autofix, proposal = { kind: 'none', reason: 'Add autofix...' }. If rule has autofix, requestBatchFix() is called (TODO: currently stubbed, returns empty previews in FRO-186 harmonize flow). LLM batch mode proposes single regex-replace or falls back to per-cell semantic fixing. FixReviewPanel displays previews.
Key files
src/lib/rules/autofix.ts:147-279src/lib/rules/autofix.ts:109-125
Autofix: per-cell semantic fixing ▶ 00:00
As a translator lead, I want to fix violations on a cell-by-cell basis when a single regex cannot work, so that complex or nuanced corrections are still automated.
How it works. When requestBatchFix() determines a single regex won't work (violatingCells < 10), it falls back to requestSurgicalFix() / semantic mode. LLM is prompted with PER_CELL_SYSTEM_PROMPT, generates per-cell { find, replace, rationale } fixes. parsePerCellResponse() validates JSON. buildPerCellProposal() generates FixPreview objects for each cell. FixReviewPanel shows before/after for each cell.
Key files
src/lib/rules/autofix.ts:127-145src/lib/rules/autofix.ts:162-172src/lib/rules/autofix.ts:289-316
Autofix: surgical fix (single cell) ▶ 00:00
As a translator, I want to automatically fix a single violation, so that I don't have to manually edit each cell.
How it works. User locates a cell with a rule violation. Clicks 'Fix' or sparkle icon. requestSurgicalFix() is called for that cell + rule. LLM is prompted with PER_CELL_SYSTEM_PROMPT, receives cell details. Proposed fix is shown in FixReviewPanel. User can accept or decline. Accepted fix is applied to cell.
Key files
src/lib/rules/autofix.ts:281-316src/components/RuleDrawer.tsx:88-90
Rule statistics (usage summary) ▶ 00:00
As a translator lead, I want to see project-wide fix statistics, so that I can track the impact of automation.
How it works. User navigates to Rules page (RulesPage). Top of content shows usage summary line: '{fixesApplied} fixes applied · {llmCalls} LLM calls this project'. Tooltip on hover explains metrics. Stats update as fixes are applied.
Key files
src/components/RulesPage.tsx:133-138src/components/RulesPage.tsx:190-194
Builtin check: empty target ▶ 00:00
As a translator, I want to be alerted when a translation is empty, so that I don't miss untranslated cells.
How it works. Builtin check 'empty-target' runs on all cells, even with empty target. If cell.status === 'empty' or target is whitespace-only, and source has content, violation is raised. Severity: major (default), enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:25-34src/lib/lqa/check-functions/empty-target.ts
Builtin check: target equals source ▶ 00:00
As a translator, I want to catch untranslated content that appears translated, so that I can fix verbatim copies that slip through.
How it works. Builtin check 'target-equals-source' runs on non-empty cells. If cell.translated matches cell.original verbatim, violation is raised. Severity: major, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:35-44src/lib/lqa/check-functions/target-equals-source.ts
Builtin check: placeholder integrity ▶ 00:00
As a translator, I want to ensure placeholders ({name}, <tag>, %s, \n) are preserved, so that dynamic content is not lost.
How it works. Builtin check 'placeholder-integrity' runs on cells. Extracts tokens like {name}, <tag>, %s, \n from source. If any token is missing from target, violation is raised. Severity: major, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:45-54src/lib/lqa/check-functions/placeholder-integrity.ts
Builtin check: number integrity ▶ 00:00
As a translator, I want to ensure numbers in the source appear in the target, so that numerical data is preserved.
How it works. Builtin check 'number-integrity' runs on cells. Extracts numerals/numbers from source. If any numeral is missing from target (accounting for locale separator variations), violation is raised. Severity: major, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:55-64src/lib/lqa/check-functions/number-integrity.ts
Builtin check: end punctuation mismatch ▶ 00:00
As a translator, I want translations to end with the same punctuation as the source, so that translations are stylistically consistent.
How it works. Builtin check 'end-punctuation-mismatch' runs on cells. If source ends with ?/!/. and target does not end with the same, violation is raised. Severity: minor, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:65-74src/lib/lqa/check-functions/end-punctuation-mismatch.ts
Builtin check: extra whitespace ▶ 00:00
As a translator, I want to catch leading/trailing spaces and double spaces, so that formatting is clean.
How it works. Builtin check 'double-space' runs on cells. If target has leading/trailing whitespace or multiple consecutive spaces, violation is raised. Severity: minor, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:75-84src/lib/lqa/check-functions/double-space.ts
Builtin check: repeated word ▶ 00:00
As a translator, I want to catch accidental word repetition in translations, so that typos are prevented.
How it works. Builtin check 'repeated-word' runs on cells. If the same word appears twice consecutively in target (unless source also has the repetition), violation is raised. Severity: minor, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:85-94src/lib/lqa/check-functions/repeated-word.ts
Builtin check: unpaired symbols ▶ 00:00
As a translator, I want to detect mismatched brackets, parentheses, and braces, so that markup is not corrupted.
How it works. Builtin check 'unpaired-symbols' runs on cells. Counts (, [, { in source/target and checks target has equal counts. Mismatched delimiters raise violation. Severity: minor, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:95-104src/lib/lqa/check-functions/unpaired-symbols.ts
Builtin check: abbreviation mismatch ▶ 00:00
As a translator, I want to catch when abbreviations change between source and target, so that acronyms and short forms are consistent.
How it works. Builtin check 'abbreviation-mismatch' runs on cells. Extracts abbreviations (sequences of capitals) from source and checks target contains same abbreviations. Missing abbreviations raise violation. Severity: minor, enabled by default.
Key files
src/lib/lqa/builtin-registry.ts:105-110src/lib/lqa/check-functions/abbreviation-mismatch.ts