Field tracking and highlighting system for Legal Markdown JS, providing visual indicators and detailed reports for template variable usage and document completeness.
- Overview
- Output Format Behavior
- Span Generation
- Field Reports
- Configuration
- CLI Usage
- Programmatic Usage
- Troubleshooting
- Best Practices
Field tracking in Legal Markdown JS provides visibility into template variable usage, helping identify missing data, validate document completeness, and facilitate document review workflows.
- Visual highlighting - Color-coded indicators for different field states
- Detailed reporting - Field usage statistics via
fieldReport - Format-specific behavior - Tracking auto-enabled for HTML/PDF, opt-in for markdown
- AST-based accuracy - Remark plugin processes fields within the AST, skipping code blocks
Field tracking behavior differs based on the target output format.
Default Behavior: Clean output (tracking disabled)
- Maintains compatibility with Ruby LegalMarkdown
- Produces clean markdown suitable for further processing
- No HTML spans or tracking markup
Enabled Behavior: Tracking with HTML spans
# Enable tracking in markdown output
legal-md --enable-field-tracking document.md output.md<!-- Input -->
Client: {{client_name}} Amount: {{formatCurrency(amount, "EUR")}}
<!-- Output with tracking enabled -->
Client: <span class="legal-field imported-value" data-field="client_name">Acme
Corp</span> Amount:
<span class="legal-field highlight" data-field="amount">1,234.56 EUR</span>Default Behavior: Field tracking is always enabled
generateHtml() forces enableFieldTracking: true automatically. No opt-in
required.
# HTML generation with field highlighting
legal-md --html --highlight document.mdDefault Behavior: Field tracking is always enabled
generatePdf() forces enableFieldTracking: true automatically. No opt-in
required.
# PDF generation with field highlighting
legal-md --pdf --highlight document.mdSpans are generated by src/extensions/tracking/field-span.ts via
fieldSpan(fieldName, content, kind). The kind parameter controls the
compound CSS class emitted:
| Kind | Compound class |
|---|---|
imported |
legal-field imported-value |
missing |
legal-field missing-value |
highlight |
legal-field highlight |
crossref |
legal-field highlight |
All spans carry a data-field attribute with the template variable name,
enabling JavaScript-based field inspection and automation.
For top-level helper calls, data-field follows this rule:
- If exactly one source field path is detected in helper arguments, use that path.
- If zero or multiple source fields are detected, fallback to the helper name.
Examples:
{{titleCase provider.contact.name}}->data-field="provider.contact.name"{{max 10 25}}->data-field="max"(no source field path){{concat code "-" suffix}}->data-field="concat"(multiple source fields)
<!-- Filled field -->
<span class="legal-field imported-value" data-field="client_name"
>Acme Corporation</span
>
<!-- Field processed with helper -->
<span class="legal-field highlight" data-field="amount">$50,000.00</span>
<!-- Missing field -->
<span class="legal-field missing-value" data-field="missing_data"
>[[missing_data]]</span
>| Color | Class | Description |
|---|---|---|
| Blue | imported-value |
Successfully filled fields |
| Red | missing-value |
Required fields without values |
| Yellow | highlight |
Helper functions or complex expressions |
Default styling for field highlighting:
.imported-value {
background-color: #e3f2fd;
border: 1px solid #2196f3;
padding: 2px 4px;
border-radius: 3px;
}
.missing-value {
background-color: #ffebee;
border: 1px solid #f44336;
padding: 2px 4px;
border-radius: 3px;
}
.highlight {
background-color: #fff3e0;
border: 1px solid #ff9800;
padding: 2px 4px;
border-radius: 3px;
}The processor returns a fieldReport object with field tracking statistics.
// From the processor result
interface FieldReport {
totalFields: number; // Total field occurrences processed
uniqueFields: number; // Number of distinct field names
fields: Map<string, TrackedField>; // Map of field name to tracked state
}
type FieldStatus = 'filled' | 'empty' | 'logic';
interface TrackedField {
name: string; // Field name (e.g., 'client.name')
status: FieldStatus; // filled | empty | logic
value?: YamlValue; // Resolved value, if available
originalValue?: YamlValue; // Value before processing
hasLogic: boolean; // True when field uses helpers or conditional logic
mixinUsed?: string; // Name of mixin used for processing
}import { processLegalMarkdown } from 'legal-markdown-js';
const result = await processLegalMarkdown(content, {
enableFieldTracking: true,
});
console.log(result.fieldReport);
// {
// totalFields: 15,
// uniqueFields: 12,
// fields: Map {
// 'client_name' => { name: 'client_name', status: 'filled', value: 'Acme Corp', hasLogic: false },
// 'amount' => { name: 'amount', status: 'logic', value: '$50,000', hasLogic: true },
// 'due_date' => { name: 'due_date', status: 'empty', value: undefined, hasLogic: false }
// }
// }function analyzeFieldReport(report) {
const allFields = Array.from(report.fields.values());
const filledCount = allFields.filter(f => f.status === 'filled').length;
const emptyCount = allFields.filter(f => f.status === 'empty').length;
const logicCount = allFields.filter(f => f.status === 'logic').length;
const completeness = (filledCount / report.uniqueFields) * 100;
console.log(`Document Completeness: ${completeness.toFixed(1)}%`);
console.log(`Fields Filled: ${filledCount}/${report.uniqueFields}`);
console.log(`Missing Fields: ${emptyCount}`);
console.log(`Complex Fields: ${logicCount}`);
// List missing fields
const missingFields = allFields.filter(f => f.status === 'empty');
if (missingFields.length > 0) {
console.log('\nMissing Fields:');
missingFields.forEach(field => {
console.log(`- ${field.name}`);
});
}
}Field tracking is controlled by these options:
| Option | Type | Default | Description |
|---|---|---|---|
enableFieldTracking |
boolean | false | Enable field tracking in markdown |
astFieldTracking |
boolean | false | Emit internal tracking tokens in Phase 2, render in 3 |
logicBranchHighlighting |
boolean | false | Annotate winner branches for conditional Handlebars |
For HTML and PDF output, field tracking is always enabled automatically by
generateHtml() and generatePdf().
| Output Format | Default Behavior | How to Enable |
|---|---|---|
| Markdown | Clean output (no tracking) | enableFieldTracking: true option |
| HTML | Always enabled | Automatic (forced by generateHtml) |
| Always enabled | Automatic (forced by generatePdf) |
# Enable field tracking in markdown output
legal-md --enable-field-tracking contract.md reviewed-contract.md
# Generate HTML with automatic field tracking
legal-md --html --highlight contract.md
# Generate PDF with automatic field tracking
legal-md --pdf --highlight contract.md
# Field tracking with metadata export
legal-md --enable-field-tracking --export-json contract.md processed-contract.mdimport { processLegalMarkdown } from 'legal-markdown-js';
const result = await processLegalMarkdown(content, {
enableFieldTracking: true,
});
// Access field report
if (result.fieldReport) {
const allFields = Array.from(result.fieldReport.fields.values());
const filledCount = allFields.filter(f => f.status === 'filled').length;
const total = result.fieldReport.uniqueFields;
console.log(
`Document ${total > 0 ? (filledCount / total) * 100 : 0}% complete`
);
}import { generateHtml } from 'legal-markdown-js';
// Field tracking is always enabled for HTML
const result = await generateHtml(content);
// The result includes fieldReport automatically
const report = result.fieldReport;
if (report) {
for (const [name, field] of report.fields) {
console.log(`${name}: ${field.status} (hasLogic=${field.hasLogic})`);
}
}async function validateDocument(content, requiredFields) {
const result = await generateHtml(content);
const missingRequired = requiredFields.filter(field => {
const tracked = result.fieldReport?.fields.get(field);
return !tracked || tracked.status !== 'filled';
});
if (missingRequired.length > 0) {
throw new Error(`Missing required fields: ${missingRequired.join(', ')}`);
}
return result;
}Field tracking not visible in markdown output:
# Solution: Explicitly enable tracking
legal-md --enable-field-tracking document.md output.mdField tracking is disabled by default for markdown output to maintain Ruby LegalMarkdown compatibility.
Fields in code blocks being tracked:
The remark plugin skips code blocks and inline code automatically. If you see fields being tracked inside code blocks, ensure the markdown syntax is valid (proper backtick fencing).
# Enable debug mode to see field tracking details
legal-md --debug --enable-field-tracking document.mdDebug mode logs field tracking counts, plugin execution details, and processing time.
const result = await generateHtml(template, data);
const report = result.fieldReport;
const allFields = report ? Array.from(report.fields.values()) : [];
const emptyCount = allFields.filter(f => f.status === 'empty').length;
if (emptyCount > 0) {
console.warn(`Document incomplete: ${emptyCount} missing fields`);
}// Step 1: Generate with highlighting (always on for HTML)
const draftResult = await generateHtml(template, partialData);
// Step 2: Review field report for missing data
const allFields = Array.from(draftResult.fieldReport.fields.values());
const missingFields = allFields
.filter(f => f.status === 'empty')
.map(f => f.name);
// Step 3: Fill missing data and generate final output
const finalData = { ...partialData, ...additionalData };
const finalResult = await generatePdf(template, finalData);For markdown output intended for further processing (e.g., piping to pandoc), leave field tracking disabled (the default). Only enable it when you need visual review of field status in the markdown itself.
src/extensions/tracking/field-tracker.ts- FieldTracker classsrc/extensions/tracking/field-span.ts- HTML span generationsrc/core/tracking/field-state.ts- Core field state interfaces and enumssrc/plugins/remark/template-fields.ts- Remark plugin for field processing
- Remark Processing - AST-based processing pipeline
- Field Highlighting - Visual highlighting details
- HTML Generation - HTML output with field tracking