Skip to content

Latest commit

 

History

History
422 lines (312 loc) · 12.3 KB

File metadata and controls

422 lines (312 loc) · 12.3 KB

Field Tracking

Field tracking and highlighting system for Legal Markdown JS, providing visual indicators and detailed reports for template variable usage and document completeness.

Table of Contents

Overview

Field tracking in Legal Markdown JS provides visibility into template variable usage, helping identify missing data, validate document completeness, and facilitate document review workflows.

Key Features

  • 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

Output Format Behavior

Field tracking behavior differs based on the target output format.

Markdown Output

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>

HTML Output

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.md

PDF Output

Default 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.md

Span Generation

Spans 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.

data-field policy for helpers

For top-level helper calls, data-field follows this rule:

  1. If exactly one source field path is detected in helper arguments, use that path.
  2. 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)

HTML Structure

<!-- 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
>

Highlight Colors

Color Class Description
Blue imported-value Successfully filled fields
Red missing-value Required fields without values
Yellow highlight Helper functions or complex expressions

CSS Classes

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;
}

Field Reports

The processor returns a fieldReport object with field tracking statistics.

Report Structure

// 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
}

Report Example

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 }
//   }
// }

Report Analysis

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}`);
    });
  }
}

Configuration

Processing Options

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().

Field Tracking Behavior by Format

Output Format Default Behavior How to Enable
Markdown Clean output (no tracking) enableFieldTracking: true option
HTML Always enabled Automatic (forced by generateHtml)
PDF Always enabled Automatic (forced by generatePdf)

CLI Usage

# 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.md

Programmatic Usage

Basic Usage

import { 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`
  );
}

HTML Generation

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})`);
  }
}

Document Validation

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;
}

Troubleshooting

Common Issues

Field tracking not visible in markdown output:

# Solution: Explicitly enable tracking
legal-md --enable-field-tracking document.md output.md

Field 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).

Debug Mode

# Enable debug mode to see field tracking details
legal-md --debug --enable-field-tracking document.md

Debug mode logs field tracking counts, plugin execution details, and processing time.

Best Practices

1. Use Field Reports for Validation

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`);
}

2. Review Workflow

// 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);

3. Keep Markdown Output Clean

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.

Source Files

  • src/extensions/tracking/field-tracker.ts - FieldTracker class
  • src/extensions/tracking/field-span.ts - HTML span generation
  • src/core/tracking/field-state.ts - Core field state interfaces and enums
  • src/plugins/remark/template-fields.ts - Remark plugin for field processing

See Also