# Receipt Template Language (RTL) - Project Specification

---

# Phase 1: Core Template Engine

## 1. Overview

A file-based template system for configuring receipt layouts per receipt type and printer model. Replaces hardcoded C# converters with editable `.template` files.

### Deliverables
- New project: `Inspectron.Epson.Templates`
- Template language lexer, parser, and interpreter
- File-based configuration loader
- Integration with existing `IReceiptConverterFactory`
- Fallback template for undefined combinations
- Unit tests

---

## 2. Project Structure

```
Inspectron.Epson.Templates/
├── Inspectron.Epson.Templates.csproj
├── Language/
│   ├── Lexer.cs
│   ├── Parser.cs
│   ├── Tokens.cs
│   └── Nodes/
│       ├── TemplateNode.cs
│       ├── TextNode.cs
│       ├── StyledTextNode.cs
│       ├── BindingNode.cs
│       ├── SeparatorNode.cs
│       ├── RowNode.cs
│       ├── ColumnNode.cs
│       ├── IfNode.cs
│       ├── ForeachNode.cs
│       └── EmptyLineNode.cs
├── Interpreter/
│   ├── TemplateInterpreter.cs
│   ├── DataContext.cs
│   └── InterpreterException.cs
├── Configuration/
│   ├── TemplateConfiguration.cs
│   ├── TemplateAssignment.cs
│   ├── PrinterProfile.cs
│   ├── PrinterProfileRegistry.cs
│   └── TemplateResolver.cs
├── Storage/
│   ├── ITemplateStorage.cs
│   └── FileTemplateStorage.cs
├── TemplateEngine.cs
├── TemplateReceiptConverter.cs
└── TemplateReceiptConverterFactory.cs
```

---

## 3. File System Layout

```
templates/
├── kitchen-default.template        # Default kitchen receipt
├── kitchen-u220.template           # Kitchen receipt for TM-U220II
├── bar-default.template            # Bar receipt
├── final-receipt.template          # Customer receipt
├── orders-overview.template        # Orders overview
├── fallback.template               # Used when no match found
├── assignments.json                # Maps (receiptType, printer) → template
└── samples/                        # Sample data for preview/testing
    ├── kitchen-default.json
    ├── bar-default.json
    ├── final-receipt.json
    └── orders-overview.json
```

---

## 4. Template Language Specification

### 4.1 Line Types

| Line Type | Syntax | Example |
|-----------|--------|---------|
| Styled Text | `#styles# content` | `#bold,center# {title}` |
| Plain Text | `content` | `Hello World` |
| Separator | `---`, `===`, `***`, `~~~` | `---` |
| Row Start | `@row ratios [styles]` | `@row 60:40 bold` |
| Row Content | `content \| content` | `{name} \| >{price}` |
| Row End | `@endrow` | `@endrow` |
| Condition | `@if condition` | `@if comment` |
| Loop | `@foreach item in collection` | `@foreach dish in dishes` |
| Block End | `@end` | `@end` |
| Empty | *(blank line)* | |

### 4.2 Styles

| Style | Description |
|-------|-------------|
| `bold` | Bold text |
| `big` | Large font (2x magnification) |
| `red` | Red color (two-color printers only) |
| `center` | Center-aligned |
| `right` | Right-aligned |
| `spacing:N` | Set line spacing (0-255) |
| `spacing:default` | Reset to default spacing |

**Syntax:** `#style1,style2,...# content`

**Examples:**
```
#bold,center,big# {title}
#red# WARNING
#spacing:0,bold# Compact line
```

### 4.3 Data Binding

**Syntax:** `{path}` or `{path:format}`

**Path navigation:**
```
{title}                 // Root property
{order.date}            // Nested property
{items.count}           // Collection count
```

**Format specifiers:**
```
{price:F2}              // Decimal, 2 places → "12.50"
{date:dd-MMM-yy}        // Date → "25-Jan-26"
{date:HH:mm}            // Time → "14:30"
{number:D4}             // Padded integer → "0042"
```

**Missing data behavior:** Returns empty string (silent).

### 4.4 Separators

```
---     // Dashed line (fills line width)
===     // Equals line
***     // Asterisk line
~~~     // Tilde line
```

Minimum 3 characters. Fills `PrinterProfile.LineWidth`.

### 4.5 Rows (Columns)

**Multi-line syntax:**
```
@row 60:40
{leftContent} | >{rightContent}
@endrow
```

**Single-line shorthand:**
```
@row 60:40 | {left} | >{right} |
```

**Ratios:**
```
@row 50:50           // Two equal columns
@row 60:30:10        // Three columns (percentages)
@row *:20            // First fills remainder, second is 20%
@row 30:*:20         // Middle fills remainder
```

**Column alignment (prefix in content):**
```
{text}      // Left-aligned (default)
>{text}     // Right-aligned
^{text}     // Center-aligned
```

**Row styles (optional, applied to all columns):**
```
@row 50:50 bold
@row 70:30 big,red
```

### 4.6 Conditionals

**Syntax:**
```
@if condition
  content
@end
```

**Conditions:**
```
@if propertyName              // Truthy (not null, not empty, not false, not 0)
@if !propertyName             // Falsy
@if items.count > 0           // Comparison
@if status == "active"        // String equality
@if total >= 100              // Numeric comparison
```

**Operators:** `==`, `!=`, `>`, `<`, `>=`, `<=`

### 4.7 Loops

**Syntax:**
```
@foreach item in collection
  content using {item.property}
@end
```

**Loop variables:**
```
{item}              // Current item (if primitive)
{item.property}     // Item property
{item._index}       // 0-based index
{item._number}      // 1-based number
{item._first}       // true if first iteration
{item._last}        // true if last iteration
```

**Nesting:**
```
@foreach gang in gangs
  #red# {gang.name}
  @foreach dish in gang.dishes
    {dish.number}x {dish.name}
  @end
@end
```

---

## 5. Configuration

### 5.1 assignments.json

```json
{
  "assignments": [
    {
      "receiptType": "Receipt",
      "printerProfile": null,
      "templateFile": "final-receipt.template"
    },
    {
      "receiptType": "WorkareaTicket",
      "printerProfile": null,
      "templateFile": "kitchen-default.template"
    },
    {
      "receiptType": "WorkareaTicket",
      "printerProfile": "tm-u220ii",
      "templateFile": "kitchen-u220.template"
    },
    {
      "receiptType": "NextCourse",
      "printerProfile": null,
      "templateFile": "kitchen-default.template"
    },
    {
      "receiptType": "OrdersOverview",
      "printerProfile": null,
      "templateFile": "orders-overview.template"
    }
  ],
  "fallbackTemplate": "fallback.template"
}
```

### 5.2 Printer Profiles (built-in)

| Profile ID | Printer | LineWidth | BigLineWidth | SupportsRed |
|------------|---------|-----------|--------------|-------------|
| `tm-t30iii` | TM-T30III | 48 | 24 | false |
| `tm-u220ii` | TM-U220II | 33 | 20 | true |

Mapped from printer ID byte:
- `0x01` → `tm-t30iii`
- `0x13` → `tm-u220ii`

### 5.3 Resolution Priority

1. Exact match: `receiptType` + `printerProfile`
2. Type only: `receiptType` + `printerProfile: null`
3. Fallback: `fallbackTemplate`

---

## 6. Public API

### 6.1 TemplateEngine

```csharp
namespace Inspectron.Epson.Templates;

public class TemplateEngine
{
    public TemplateEngine(string templatesDirectory);

    /// <summary>
    /// Renders a template with the given data.
    /// </summary>
    public List<PrintCommand> Render(
        string templateContent,
        string jsonData,
        PrinterProfile profile);

    /// <summary>
    /// Parses template and returns errors/warnings (for validation).
    /// </summary>
    public TemplateValidationResult Validate(string templateContent);
}

public class TemplateValidationResult
{
    public bool IsValid { get; }
    public List<TemplateError> Errors { get; }
    public List<TemplateWarning> Warnings { get; }
}

public class TemplateError
{
    public int Line { get; }
    public int Column { get; }
    public string Message { get; }
    public string Code { get; }  // e.g., "RTL001"
}

public class TemplateWarning
{
    public int Line { get; }
    public int Column { get; }
    public string Message { get; }
    public string Code { get; }  // e.g., "RTL100"
}
```

### 6.2 TemplateReceiptConverterFactory

```csharp
namespace Inspectron.Epson.Templates;

public class TemplateReceiptConverterFactory : IReceiptConverterFactory
{
    public TemplateReceiptConverterFactory(string templatesDirectory);

    /// <summary>
    /// Creates a converter for the given receipt type and printer.
    /// Falls back to fallback template if no match found.
    /// </summary>
    public IReceiptConverter Create(int receiptType, byte printerId);

    /// <summary>
    /// Reloads templates and assignments from disk.
    /// </summary>
    public void Reload();
}
```

### 6.3 Integration Example

```csharp
// In EpsonPrintService startup
var converterFactory = new TemplateReceiptConverterFactory("./templates");

// In DI registration (Ninject)
kernel.Bind<IReceiptConverterFactory>()
    .ToConstant(converterFactory)
    .InSingletonScope();

// Usage remains unchanged
var converter = converterFactory.Create(receiptType, printerId);
var commands = converter.Convert(jsonContent);
```

---

## 7. Error Codes

### Errors (prevent rendering)

| Code | Message |
|------|---------|
| RTL001 | Unexpected token '{token}' at line {line} |
| RTL002 | Unclosed block '@{block}' started at line {line} |
| RTL003 | '@end' without matching '@if' or '@foreach' |
| RTL004 | Invalid style '{style}' |
| RTL005 | Invalid ratio format '{ratio}' |
| RTL006 | Column count mismatch: expected {expected}, got {actual} |
| RTL007 | Invalid condition syntax |
| RTL008 | Invalid binding path '{path}' |
| RTL009 | Unclosed binding '{' at line {line} |
| RTL010 | Unclosed style marker '#' at line {line} |

### Warnings (render anyway)

| Code | Message |
|------|---------|
| RTL100 | Unused loop variable '{variable}' |
| RTL101 | Empty @if block |
| RTL102 | Empty @foreach block |
| RTL103 | Style '{style}' has no effect on this printer |

---

## 8. Default Templates

Provide default templates matching current hardcoded behavior:

- `kitchen-default.template` - matches `KitchenReceiptConverter`
- `kitchen-u220.template` - compact version for narrow printers
- `bar-default.template` - matches `BarReceiptConverter`
- `final-receipt.template` - matches `FinalReceiptConverter`
- `orders-overview.template` - matches `OrderItemsReceiptConverter`
- `fallback.template` - minimal template that dumps JSON fields

---

## 9. Testing

### Unit Tests

```
Inspectron.Epson.Templates.Tests/
├── Lexer/
│   ├── LexerBasicTests.cs
│   ├── LexerStyleTests.cs
│   ├── LexerBindingTests.cs
│   └── LexerControlFlowTests.cs
├── Parser/
│   ├── ParserBasicTests.cs
│   ├── ParserRowTests.cs
│   ├── ParserNestingTests.cs
│   └── ParserErrorTests.cs
├── Interpreter/
│   ├── InterpreterTextTests.cs
│   ├── InterpreterBindingTests.cs
│   ├── InterpreterConditionTests.cs
│   ├── InterpreterLoopTests.cs
│   └── InterpreterRowTests.cs
├── Configuration/
│   ├── TemplateResolverTests.cs
│   └── PrinterProfileTests.cs
└── Integration/
    ├── KitchenReceiptTests.cs
    ├── FinalReceiptTests.cs
    └── FallbackTests.cs
```

### Test Coverage Goals

- Lexer: All token types, edge cases, error handling
- Parser: All node types, nesting, malformed input
- Interpreter: All features, format specifiers, missing data
- Integration: Match output of existing converters

---

## 10. Migration Plan

1. Create `Inspectron.Epson.Templates` project
2. Implement and test template engine
3. Create default templates matching current output
4. Add `TemplateReceiptConverterFactory` to DI as alternative
5. Integration test: compare output of both factories
6. Switch DI registration to use template factory
7. Remove legacy converter code (optional, can keep as reference)

---

# Phase 2: Template Editor (VS Code Extension + C# Language Server)

## 1. Overview

VS Code extension for creating and editing receipt templates with live preview, syntax highlighting, autocomplete, and validation. The extension uses a **C# Language Server** (LSP) that reuses the lexer, parser, and interpreter from Phase 1. The VS Code extension is a thin TypeScript client that connects to the language server.

### Architecture

```
┌─────────────────────────────────────────────────────────────────────────┐
│                              VS Code                                     │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  vscode-rtl (TypeScript)                                          │  │
│  │  - Language client (connects to LSP)                              │  │
│  │  - Preview webview (renders HTML)                                 │  │
│  │  - Assignments webview                                            │  │
│  └───────────────────────────────────────────────────────────────────┘  │
│                                    │ LSP (JSON-RPC over stdio)          │
│                                    ▼                                     │
│  ┌───────────────────────────────────────────────────────────────────┐  │
│  │  Inspectron.Epson.Templates.LanguageServer (C#)                   │  │
│  │  - Semantic tokens (syntax highlighting)                          │  │
│  │  - Completion provider                                            │  │
│  │  - Diagnostics (validation)                                       │  │
│  │  - Hover provider                                                 │  │
│  │  - Folding ranges                                                 │  │
│  │  - Document symbols                                               │  │
│  │  - Custom requests (preview rendering)                            │  │
│  │                                                                   │  │
│  │  References: Inspectron.Epson.Templates (Phase 1)                 │  │
│  └───────────────────────────────────────────────────────────────────┘  │
└─────────────────────────────────────────────────────────────────────────┘
```

### Deliverables
- C# Language Server: `Inspectron.Epson.Templates.LanguageServer`
- VS Code extension: `vscode-rtl` (thin TypeScript client)
- Language registration for `.template` files
- Semantic token provider for syntax highlighting (via LSP)
- Completion provider (via LSP)
- Diagnostic provider for real-time validation (via LSP)
- Webview panel for live preview with sample data
- Webview panel for template assignment management

### Advantages
- **No code duplication** - reuses Phase 1 lexer, parser, interpreter
- **Single source of truth** - language behavior defined once in C#
- **Mature editing features** - VS Code provides find/replace, multi-cursor, undo/redo, git
- **Cross-platform** - .NET 8 runs on Windows, macOS, Linux
- **Easy distribution** - VSIX bundles extension + language server executable

---

## 2. Project Structure

### 2.1 Language Server (C#)

```
Inspectron.Epson.Templates.LanguageServer/
├── Inspectron.Epson.Templates.LanguageServer.csproj
├── Program.cs                          # Entry point, stdio server setup
├── RtlLanguageServer.cs                # Main server class
├── Handlers/
│   ├── TextDocumentSyncHandler.cs      # Document open/change/close
│   ├── SemanticTokensHandler.cs        # Syntax highlighting
│   ├── CompletionHandler.cs            # Autocomplete
│   ├── DiagnosticHandler.cs            # Validation errors/warnings
│   ├── HoverHandler.cs                 # Hover information
│   ├── FoldingRangeHandler.cs          # Code folding
│   ├── DocumentSymbolHandler.cs        # Outline/breadcrumbs
│   └── CustomRequestHandlers.cs        # Preview rendering, schema extraction
├── Services/
│   ├── DocumentManager.cs              # Track open documents
│   ├── DiagnosticService.cs            # Run validation, publish diagnostics
│   ├── CompletionService.cs            # Build completion items
│   ├── SemanticTokenService.cs         # Generate semantic tokens
│   ├── SchemaService.cs                # Extract schema from sample JSON
│   └── ScopeTracker.cs                 # Track @foreach loop variables
└── Models/
    ├── RtlSemanticTokenTypes.cs        # Token type definitions
    └── RtlSemanticTokenModifiers.cs    # Token modifier definitions
```

### 2.2 VS Code Extension (TypeScript - thin client)

```
vscode-rtl/
├── package.json                    # Extension manifest
├── tsconfig.json                   # TypeScript configuration
├── webpack.config.js               # Bundler configuration
├── .vscodeignore                   # Files to exclude from package
├── language-configuration.json     # Bracket matching, auto-closing pairs
├── snippets/
│   └── rtl.snippets.json           # Code snippets (@if, @foreach, @row)
├── src/
│   ├── extension.ts                # Extension entry point
│   ├── languageClient.ts           # LSP client setup and management
│   ├── preview/
│   │   ├── previewPanel.ts         # Webview panel management
│   │   └── previewStyles.css       # Receipt visual styling
│   ├── assignments/
│   │   └── assignmentsPanel.ts     # Assignments editor webview
│   └── utils/
│       └── serverPath.ts           # Locate language server executable
├── media/
│   ├── preview.css                 # Receipt preview styling
│   ├── assignments.css             # Assignments editor styling
│   └── icons/
│       ├── template.svg            # File icon
│       └── preview.svg             # Preview button icon
├── server/                         # Bundled language server (published .exe/.dll)
│   └── (built from C# project)
└── test/
    └── extension.test.ts           # Basic extension activation tests
```

---

## 3. UI Layout

The extension integrates with VS Code's native UI:

```
┌─────────────────────────────────────────────────────────────────────────────┐
│  File   Edit   Selection   View   Go   Run   Terminal   Help                │
├─────────────────────────────────────────────────────────────────────────────┤
│ EXPLORER            │ kitchen-default.template              │ RTL Preview   │
│ ┌─────────────────┐ │ ─────────────────────────────────────│───────────────│
│ │ ▼ TEMPLATES     │ │  1│#bold,center,big# {title}         │┌─────────────┐│
│ │   kitchen-def...│ │  2│---                               ││ Warme Küche ││
│ │   kitchen-u220  │ │  3│                                  ││─────────────││
│ │   bar-default   │ │  4│@row 60:40                        ││             ││
│ │   final-receipt │ │  5│{date:dd-MMM} | >{orderNumber}    ││25-Jan Nr:42 ││
│ │   orders-overv..│ │  6│@endrow                           ││             ││
│ │   fallback      │ │  7│#center# {waiter}                 ││Max Musterma.││
│ │                 │ │  8│                                  ││             ││
│ │ ▼ SAMPLES       │ │  9│@foreach dish in dishes           ││Tisch: 12    ││
│ │   kitchen-def...│ │ 10│  {dish.quantity}x {dish.name}    ││─────────────││
│ │   bar-default...│ │ 11│@end                              ││2x Tomatensup││
│ │                 │ │                                      ││1x Schnitzel ││
│ │ ▼ RTL ASSIGNMENTS│ │                                      ││  - Pommes   ││
│ │   Receipt → ...│ │                                      ││  + Reis     ││
│ │   WorkareaTic..│ │                                      │└─────────────┘│
│ │   NextCourse →.│ │                                      │               │
│ └─────────────────┘ │                                      │Printer: ▼    │
│                     │                                      │ TM-T30III    │
├─────────────────────┴──────────────────────────────────────┴───────────────┤
│ PROBLEMS  OUTPUT  DEBUG CONSOLE  TERMINAL                                   │
│ ┌──────────────────────────────────────────────────────────────────────────┐│
│ │ ⚠ kitchen-default.template [2]                                          ││
│ │   ⚠ Line 10: Unused loop variable 'dish' in nested scope       [RTL100] ││
│ │ ❌ fallback.template [1]                                                 ││
│ │   ❌ Line 5: Unclosed '@foreach' block started at line 3        [RTL002] ││
│ └──────────────────────────────────────────────────────────────────────────┘│
├─────────────────────────────────────────────────────────────────────────────┤
│ RTL │ Ln 7, Col 12 │ UTF-8 │ CRLF │ {} kitchen-default.json │ ✓           │
└─────────────────────────────────────────────────────────────────────────────┘
```

### UI Components

| Component | VS Code Feature | Implementation |
|-----------|-----------------|----------------|
| Template list | Explorer tree view | Native file explorer or custom TreeDataProvider |
| Sample data list | Explorer tree view | Custom TreeDataProvider |
| Assignments list | Explorer tree view | Custom TreeDataProvider with inline editing |
| Editor | Native text editor | Language client connects to C# language server |
| Syntax highlighting | Semantic tokens | Language server provides tokens via LSP |
| Autocomplete | Completion items | Language server provides completions via LSP |
| Validation | Problems panel | Language server publishes diagnostics via LSP |
| Preview | Webview panel | TypeScript requests rendered HTML from language server |
| Status bar | Status bar items | Shows current sample file, printer profile |

---

## 4. Features

### 4.1 Language Registration (VS Code Extension)

**package.json contribution:**
```json
{
  "contributes": {
    "languages": [{
      "id": "rtl",
      "aliases": ["Receipt Template Language", "RTL"],
      "extensions": [".template"],
      "configuration": "./language-configuration.json",
      "icon": { "light": "./media/icons/template.svg", "dark": "./media/icons/template.svg" }
    }]
  }
}
```

**Note:** No TextMate grammar needed - syntax highlighting is provided via LSP semantic tokens.

**language-configuration.json:**
```json
{
  "comments": { "lineComment": "//" },
  "brackets": [["@if", "@end"], ["@foreach", "@end"], ["@row", "@endrow"]],
  "autoClosingPairs": [
    { "open": "{", "close": "}" },
    { "open": "#", "close": "#" }
  ],
  "surroundingPairs": [
    { "open": "{", "close": "}" },
    { "open": "#", "close": "#" }
  ]
}
```

### 4.2 Syntax Highlighting (LSP Semantic Tokens)

The language server provides semantic tokens via the `textDocument/semanticTokens/full` LSP method.

**C# Semantic Token Types (SemanticTokensHandler.cs):**

```csharp
public static class RtlSemanticTokenTypes
{
    public const string Keyword = "keyword";           // @if, @foreach, @end, @row, @endrow
    public const string Variable = "variable";         // {binding}
    public const string Property = "property";         // {object.property}
    public const string Decorator = "decorator";       // #style#
    public const string String = "string";             // Style names: bold, center
    public const string Number = "number";             // Ratios: 60:40
    public const string Operator = "operator";         // ==, !=, >, <, |, >, ^
    public const string Comment = "comment";           // Separators: ---, ===
    public const string Type = "type";                 // Format specifiers: F2, dd-MMM
}
```

**Token mapping:**

| Element | Token Type | Default Color |
|---------|------------|---------------|
| `@if`, `@foreach`, `@end` | `keyword` | Purple |
| `@row`, `@endrow` | `keyword` | Purple |
| `#...#` markers | `decorator` | Blue |
| Style names `bold`, `center` | `string` | Green |
| `{...}` bindings | `variable` | Teal |
| Property paths `.property` | `property` | Teal |
| Format specifiers `:F2` | `type` | Orange |
| Ratios `60:40` | `number` | Light Green |
| Separators `---` | `comment` | Gray |
| Column delimiter `\|` | `operator` | Yellow |
| Alignment `>`, `^` | `operator` | Orange |
| Comparison `==`, `!=` | `operator` | Red |

### 4.3 Code Folding (LSP Folding Ranges)

**FoldingRangeHandler.cs:**
```csharp
public class FoldingRangeHandler : IFoldingRangeHandler
{
    public Task<Container<FoldingRange>?> Handle(FoldingRangeRequestParam request, CancellationToken ct)
    {
        var ranges = new List<FoldingRange>();
        // Parse document, find @if...@end, @foreach...@end, @row...@endrow blocks
        // Add FoldingRange for each block
        return Task.FromResult<Container<FoldingRange>?>(new Container<FoldingRange>(ranges));
    }
}
```

Supports:
- Fold `@if...@end` blocks
- Fold `@foreach...@end` blocks
- Fold `@row...@endrow` blocks
- Nested folding

### 4.4 Autocomplete (LSP Completion)

**CompletionHandler.cs triggers:**

| Trigger | Context | Completions |
|---------|---------|-------------|
| `#` | Line start or after `#` | Style keywords: `bold`, `big`, `red`, `center`, `right`, `spacing:` |
| `,` | Inside `#...#` | Style keywords |
| `@` | Line start | Control flow: `if`, `foreach`, `end`, `row`, `endrow` |
| `{` | Anywhere | Root data properties from sample JSON + active loop variables |
| `.` | Inside `{...}` | Child properties based on path + loop metadata (`_index`, `_first`, etc.) |
| `:` | Inside `{...}` after property | Format specifiers: `F2`, `D4`, `dd-MMM-yy`, `HH:mm` |
| `in ` | After `@foreach x` | Collection properties from schema |

**CompletionService.cs features:**
- Context-aware (detects binding, style, control flow context)
- Completion item kinds (Property, Keyword, Snippet, etc.)
- Documentation for format specifiers
- Loop variable scope tracking via ScopeTracker

```csharp
public class CompletionService
{
    private readonly SchemaService _schemaService;
    private readonly ScopeTracker _scopeTracker;

    public List<CompletionItem> GetCompletions(DocumentUri uri, Position position)
    {
        var context = DetectContext(uri, position);
        return context switch
        {
            CompletionContext.Style => GetStyleCompletions(),
            CompletionContext.ControlFlow => GetControlFlowCompletions(),
            CompletionContext.Binding => GetBindingCompletions(uri, position),
            CompletionContext.BindingProperty => GetPropertyCompletions(uri, position),
            CompletionContext.BindingFormat => GetFormatCompletions(),
            _ => new List<CompletionItem>()
        };
    }
}
```

### 4.5 Diagnostics (LSP PublishDiagnostics)

**DiagnosticService.cs:**
- Validates on document open and change (debounced)
- Uses Phase 1 `TemplateEngine.Validate()` method
- Publishes diagnostics via `textDocument/publishDiagnostics`

```csharp
public class DiagnosticService
{
    private readonly TemplateEngine _templateEngine;
    private readonly ILanguageServerFacade _server;

    public void ValidateDocument(DocumentUri uri, string content)
    {
        var result = _templateEngine.Validate(content);
        var diagnostics = result.Errors
            .Select(e => new Diagnostic
            {
                Range = new Range(e.Line - 1, e.Column - 1, e.Line - 1, e.Column + 10),
                Severity = DiagnosticSeverity.Error,
                Code = e.Code,
                Message = e.Message,
                Source = "rtl"
            })
            .Concat(result.Warnings.Select(w => new Diagnostic
            {
                Range = new Range(w.Line - 1, w.Column - 1, w.Line - 1, w.Column + 10),
                Severity = DiagnosticSeverity.Warning,
                Code = w.Code,
                Message = w.Message,
                Source = "rtl"
            }))
            .ToList();

        _server.TextDocument.PublishDiagnostics(new PublishDiagnosticsParams
        {
            Uri = uri,
            Diagnostics = new Container<Diagnostic>(diagnostics)
        });
    }
}
```

### 4.6 Hover Provider (LSP Hover)

**HoverHandler.cs:**
- Hover over style → shows description
- Hover over binding → shows data type from schema
- Hover over control flow → shows syntax help
- Hover over format specifier → shows example output

```csharp
public class HoverHandler : IHoverHandler
{
    public Task<Hover?> Handle(HoverParams request, CancellationToken ct)
    {
        var content = GetHoverContent(request.TextDocument.Uri, request.Position);
        if (content == null) return Task.FromResult<Hover?>(null);

        return Task.FromResult<Hover?>(new Hover
        {
            Contents = new MarkedStringsOrMarkupContent(new MarkupContent
            {
                Kind = MarkupKind.Markdown,
                Value = content
            })
        });
    }
}
```

### 4.7 Code Snippets (VS Code Extension)

**snippets/rtl.snippets.json:**
```json
{
  "If Block": {
    "prefix": "@if",
    "body": ["@if ${1:condition}", "  $0", "@end"],
    "description": "Conditional block"
  },
  "Foreach Loop": {
    "prefix": "@foreach",
    "body": ["@foreach ${1:item} in ${2:collection}", "  $0", "@end"],
    "description": "Loop over collection"
  },
  "Row": {
    "prefix": "@row",
    "body": ["@row ${1:60}:${2:40}", "${3:left} | >${4:right}", "@endrow"],
    "description": "Two-column row"
  },
  "Styled Text": {
    "prefix": "#style",
    "body": ["#${1|bold,big,red,center,right|}# ${0:text}"],
    "description": "Styled text line"
  }
}
```

### 4.8 Preview Panel (Webview + Custom LSP Request)

The preview uses a **custom LSP request** to get rendered HTML from the language server.

**Custom request (Language Server):**
```csharp
// CustomRequestHandlers.cs
[Method("rtl/renderPreview")]
public record RenderPreviewParams
{
    public DocumentUri Uri { get; init; }
    public string PrinterProfile { get; init; }  // "tm-t30iii" or "tm-u220ii"
    public string? SampleDataPath { get; init; }
}

public record RenderPreviewResult
{
    public string Html { get; init; }           // Rendered receipt as HTML
    public string? Error { get; init; }         // Render error message if any
}

public class CustomRequestHandlers : IJsonRpcRequestHandler<RenderPreviewParams, RenderPreviewResult>
{
    public Task<RenderPreviewResult> Handle(RenderPreviewParams request, CancellationToken ct)
    {
        // 1. Load template content
        // 2. Load sample data JSON
        // 3. Get printer profile
        // 4. Call TemplateEngine.Render()
        // 5. Convert PrintCommand[] to HTML
        // 6. Return HTML or error
    }
}
```

**VS Code Extension (previewPanel.ts):**
```typescript
async function updatePreview() {
    const result = await client.sendRequest<RenderPreviewResult>('rtl/renderPreview', {
        uri: activeDocument.uri.toString(),
        printerProfile: selectedPrinter,
        sampleDataPath: selectedSamplePath
    });

    if (result.error) {
        panel.webview.html = renderError(result.error);
    } else {
        panel.webview.html = wrapInReceiptStyle(result.html);
    }
}
```

**Features:**
- Side-by-side preview (like Markdown preview)
- Printer profile selector (TM-T30III, TM-U220II)
- Live updates as template changes (debounced 300ms)
- Shows render errors inline

### 4.9 Sample Data Integration

**Custom LSP requests:**
```csharp
// Get schema from sample JSON (for autocomplete)
[Method("rtl/getSchema")]
public record GetSchemaParams { public string SampleDataPath { get; init; } }
public record GetSchemaResult { public SchemaNode Schema { get; init; } }

// List available sample files
[Method("rtl/listSamples")]
public record ListSamplesResult { public List<string> SampleFiles { get; init; } }
```

**Features:**
- Status bar item shows current sample file
- Click status bar → quick pick to change sample
- Sample JSON files in `samples/` directory
- Automatic schema extraction for autocomplete
- Sample file association by naming convention

### 4.10 Tree Views (VS Code Extension)

**Templates Tree View:**
- Lists all `.template` files
- Context menu: Rename, Delete, Duplicate, New
- Click to open

**Samples Tree View:**
- Lists all `.json` sample files
- Shows which template they're associated with
- Click to open

**Assignments Tree View:**
- Shows current assignments from `assignments.json`
- Inline display: `Receipt → final-receipt.template`
- Context menu: Edit, Delete
- "Add Assignment" button

### 4.11 Assignments Editor (Webview)

**Webview panel for editing assignments.json:**

```
┌─────────────────────────────────────────────────────────────────────┐
│ Template Assignments                                       [Save]   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│ Receipt Type        Printer         Template              Actions   │
│ ─────────────────────────────────────────────────────────────────── │
│ [Receipt        ▼]  [(Any)      ▼]  [final-receipt    ▼]  [Delete]  │
│ [WorkareaTicket ▼]  [(Any)      ▼]  [kitchen-default  ▼]  [Delete]  │
│ [WorkareaTicket ▼]  [TM-U220II  ▼]  [kitchen-u220     ▼]  [Delete]  │
│ [NextCourse     ▼]  [(Any)      ▼]  [kitchen-default  ▼]  [Delete]  │
│ [OrdersOverview ▼]  [(Any)      ▼]  [orders-overview  ▼]  [Delete]  │
│                                                                      │
│ [+ Add Assignment]                                                   │
│                                                                      │
│ Fallback Template: [fallback.template              ▼]                │
│                                                                      │
└─────────────────────────────────────────────────────────────────────┘
```

---

## 5. Data Flow

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                              VS Code                                         │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│  ┌────────────────────────────────────────────────────────────────────────┐ │
│  │                    vscode-rtl Extension (TypeScript)                   │ │
│  │                                                                        │ │
│  │  ┌──────────────┐    ┌──────────────┐    ┌──────────────────────────┐ │ │
│  │  │ Language     │    │ Preview      │    │ Assignments              │ │ │
│  │  │ Client       │    │ Webview      │    │ Webview                  │ │ │
│  │  └──────┬───────┘    └──────┬───────┘    └────────────┬─────────────┘ │ │
│  │         │                   │                         │               │ │
│  └─────────┼───────────────────┼─────────────────────────┼───────────────┘ │
│            │ LSP               │ Custom Request          │ File I/O        │
│            │ (JSON-RPC)        │ rtl/renderPreview       │                 │
│            ▼                   ▼                         ▼                 │
│  ┌─────────────────────────────────────────────────────────────────────┐   │
│  │           Language Server (C# - stdio process)                      │   │
│  │                                                                     │   │
│  │  ┌─────────────────────────────────────────────────────────────┐   │   │
│  │  │ Handlers                                                     │   │   │
│  │  │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │   │   │
│  │  │ │ Semantic   │ │ Completion │ │ Diagnostic │ │ Custom     │ │   │   │
│  │  │ │ Tokens     │ │ Handler    │ │ Handler    │ │ Requests   │ │   │   │
│  │  │ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ └─────┬──────┘ │   │   │
│  │  └───────┼──────────────┼──────────────┼──────────────┼────────┘   │   │
│  │          │              │              │              │            │   │
│  │          ▼              ▼              ▼              ▼            │   │
│  │  ┌─────────────────────────────────────────────────────────────┐   │   │
│  │  │ Services                                                     │   │   │
│  │  │ ┌────────────┐ ┌────────────┐ ┌────────────┐ ┌────────────┐ │   │   │
│  │  │ │ Document   │ │ Schema     │ │ Scope      │ │ Diagnostic │ │   │   │
│  │  │ │ Manager    │ │ Service    │ │ Tracker    │ │ Service    │ │   │   │
│  │  │ └────────────┘ └─────┬──────┘ └────────────┘ └─────┬──────┘ │   │   │
│  │  └──────────────────────┼─────────────────────────────┼────────┘   │   │
│  │                         │                             │            │   │
│  │                         ▼                             ▼            │   │
│  │  ┌─────────────────────────────────────────────────────────────┐   │   │
│  │  │ Inspectron.Epson.Templates (Phase 1 - Referenced)           │   │   │
│  │  │ ┌────────────┐ ┌────────────┐ ┌────────────┐                │   │   │
│  │  │ │ Lexer      │ │ Parser     │ │ Template   │                │   │   │
│  │  │ │            │ │            │ │ Engine     │                │   │   │
│  │  │ └────────────┘ └────────────┘ └────────────┘                │   │   │
│  │  └─────────────────────────────────────────────────────────────┘   │   │
│  │                                                                     │   │
│  └─────────────────────────────────────────────────────────────────────┘   │
│                                                                              │
└─────────────────────────────────────────────────────────────────────────────┘
```

**Key points:**
- Language Server is a standalone C# console application communicating via stdio
- All language features (highlighting, completion, validation) reuse Phase 1 code
- Custom LSP requests handle preview rendering and schema extraction
- VS Code extension is a thin client (~500 lines of TypeScript)

---

## 6. Language Server Implementation Details

### 6.1 Context Detection (CompletionService.cs)

```csharp
public enum CompletionContext
{
    None,
    Style,             // Inside #...#
    ControlFlow,       // After @ at line start
    Binding,           // Inside {...}
    BindingProperty,   // After . inside {...}
    BindingFormat,     // After : inside {...}
    ForeachCollection, // After "in" in @foreach
    RowContent         // Inside @row...@endrow
}

public class CompletionService
{
    public CompletionContext DetectContext(string line, int column)
    {
        var textBefore = line[..column];

        // Check for binding context
        var lastOpenBrace = textBefore.LastIndexOf('{');
        var lastCloseBrace = textBefore.LastIndexOf('}');
        if (lastOpenBrace > lastCloseBrace)
        {
            var bindingText = textBefore[(lastOpenBrace + 1)..];
            if (bindingText.Contains(':')) return CompletionContext.BindingFormat;
            if (bindingText.Contains('.')) return CompletionContext.BindingProperty;
            return CompletionContext.Binding;
        }

        // Check for style context
        var hashCount = textBefore.Count(c => c == '#');
        if (hashCount % 2 == 1) return CompletionContext.Style;

        // Check for control flow
        if (Regex.IsMatch(textBefore, @"^\s*@\w*$"))
            return CompletionContext.ControlFlow;

        // Check for foreach collection
        if (Regex.IsMatch(textBefore, @"@foreach\s+\w+\s+in\s+\w*$"))
            return CompletionContext.ForeachCollection;

        return CompletionContext.None;
    }
}
```

### 6.2 Schema Extraction (SchemaService.cs)

```csharp
public abstract record SchemaNode(string Name);
public record ObjectNode(string Name, List<SchemaNode> Properties) : SchemaNode(Name);
public record ArrayNode(string Name, SchemaNode? ItemType) : SchemaNode(Name);
public record ValueNode(string Name, JsonValueKind ValueKind) : SchemaNode(Name);

public class SchemaService
{
    private readonly Dictionary<string, SchemaNode> _schemaCache = new();

    public SchemaNode ExtractSchema(string jsonContent)
    {
        using var doc = JsonDocument.Parse(jsonContent);
        return AnalyzeElement("root", doc.RootElement);
    }

    private SchemaNode AnalyzeElement(string name, JsonElement element)
    {
        return element.ValueKind switch
        {
            JsonValueKind.Object => new ObjectNode(name,
                element.EnumerateObject()
                    .Select(p => AnalyzeElement(p.Name, p.Value))
                    .ToList()),

            JsonValueKind.Array => new ArrayNode(name,
                element.GetArrayLength() > 0
                    ? AnalyzeElement("item", element[0])
                    : null),

            _ => new ValueNode(name, element.ValueKind)
        };
    }

    public SchemaNode? ResolvePropertyPath(SchemaNode root, string path)
    {
        var parts = path.Split('.');
        var current = root;

        foreach (var part in parts)
        {
            current = current switch
            {
                ObjectNode obj => obj.Properties.FirstOrDefault(p => p.Name == part),
                ArrayNode arr => arr.ItemType,
                _ => null
            };
            if (current == null) return null;
        }

        return current;
    }
}
```

### 6.3 Scope Tracking (ScopeTracker.cs)

Track active `@foreach` loops to resolve loop variable bindings:

```csharp
public record LoopScope(
    string VariableName,
    string CollectionPath,
    SchemaNode? ItemSchema,
    int StartLine,
    int EndLine);

public class ScopeTracker
{
    private readonly SchemaService _schemaService;

    public List<LoopScope> GetActiveScopes(string documentContent, int line, SchemaNode? rootSchema)
    {
        var lines = documentContent.Split('\n');
        var stack = new Stack<(string Variable, string Collection, int Line)>();
        var scopes = new List<LoopScope>();

        for (int i = 0; i <= line && i < lines.Length; i++)
        {
            var currentLine = lines[i];
            var foreachMatch = Regex.Match(currentLine, @"@foreach\s+(\w+)\s+in\s+(\S+)");

            if (foreachMatch.Success)
            {
                stack.Push((foreachMatch.Groups[1].Value, foreachMatch.Groups[2].Value, i));
            }
            else if (Regex.IsMatch(currentLine, @"@end\b") && stack.Count > 0)
            {
                // Only pop if this @end is before or at the cursor line
                if (i < line)
                {
                    stack.Pop();
                }
            }
        }

        // Convert stack to scopes with schema info
        foreach (var (variable, collection, startLine) in stack)
        {
            var collectionSchema = rootSchema != null
                ? _schemaService.ResolvePropertyPath(rootSchema, collection)
                : null;

            var itemSchema = collectionSchema is ArrayNode arr ? arr.ItemType : null;

            scopes.Add(new LoopScope(variable, collection, itemSchema, startLine, -1));
        }

        return scopes;
    }

    public List<CompletionItem> GetLoopVariableCompletions(List<LoopScope> scopes)
    {
        var items = new List<CompletionItem>();

        foreach (var scope in scopes)
        {
            // Add the loop variable itself
            items.Add(new CompletionItem
            {
                Label = scope.VariableName,
                Kind = CompletionItemKind.Variable,
                Detail = $"Loop variable from @foreach {scope.VariableName} in {scope.CollectionPath}"
            });

            // Add loop metadata
            items.AddRange(new[]
            {
                new CompletionItem { Label = $"{scope.VariableName}._index", Kind = CompletionItemKind.Property, Detail = "0-based index" },
                new CompletionItem { Label = $"{scope.VariableName}._number", Kind = CompletionItemKind.Property, Detail = "1-based number" },
                new CompletionItem { Label = $"{scope.VariableName}._first", Kind = CompletionItemKind.Property, Detail = "true if first iteration" },
                new CompletionItem { Label = $"{scope.VariableName}._last", Kind = CompletionItemKind.Property, Detail = "true if last iteration" }
            });

            // Add item properties if schema is available
            if (scope.ItemSchema is ObjectNode obj)
            {
                foreach (var prop in obj.Properties)
                {
                    items.Add(new CompletionItem
                    {
                        Label = $"{scope.VariableName}.{prop.Name}",
                        Kind = CompletionItemKind.Property,
                        Detail = GetPropertyTypeDescription(prop)
                    });
                }
            }
        }

        return items;
    }
}
```

### 6.4 Semantic Tokens (SemanticTokenService.cs)

```csharp
public class SemanticTokenService
{
    // Token types must match SemanticTokensLegend sent during initialization
    private static readonly string[] TokenTypes = new[]
    {
        "keyword",    // 0: @if, @foreach, @end, @row
        "variable",   // 1: {binding}
        "property",   // 2: .property
        "decorator",  // 3: #style#
        "string",     // 4: style names
        "number",     // 5: ratios
        "operator",   // 6: |, >, ^, ==
        "comment",    // 7: separators
        "type"        // 8: format specifiers
    };

    public SemanticTokens GetSemanticTokens(string content)
    {
        var tokens = new List<int>(); // [deltaLine, deltaStart, length, tokenType, tokenModifiers]
        var lexer = new Lexer(content);
        var allTokens = lexer.Tokenize();

        int prevLine = 0;
        int prevChar = 0;

        foreach (var token in allTokens)
        {
            var tokenType = MapTokenType(token.Type);
            if (tokenType < 0) continue;

            var deltaLine = token.Line - 1 - prevLine;
            var deltaStart = deltaLine == 0 ? token.Column - 1 - prevChar : token.Column - 1;

            tokens.Add(deltaLine);
            tokens.Add(deltaStart);
            tokens.Add(token.Length);
            tokens.Add(tokenType);
            tokens.Add(0); // No modifiers

            prevLine = token.Line - 1;
            prevChar = token.Column - 1;
        }

        return new SemanticTokens { Data = tokens.ToImmutableArray() };
    }

    private int MapTokenType(TokenType type) => type switch
    {
        TokenType.If or TokenType.Foreach or TokenType.End or
        TokenType.Row or TokenType.EndRow => 0,          // keyword
        TokenType.Binding => 1,                           // variable
        TokenType.PropertyPath => 2,                      // property
        TokenType.StyleMarker => 3,                       // decorator
        TokenType.StyleName => 4,                         // string
        TokenType.Ratio => 5,                             // number
        TokenType.Operator or TokenType.ColumnDelimiter
        or TokenType.AlignmentMarker => 6,               // operator
        TokenType.Separator => 7,                         // comment
        TokenType.FormatSpecifier => 8,                   // type
        _ => -1                                           // skip
    };
}
```

---

## 7. Commands and Keybindings

### Commands (package.json)

```json
{
  "contributes": {
    "commands": [
      {
        "command": "rtl.openPreview",
        "title": "Open Preview",
        "category": "RTL",
        "icon": "$(open-preview)"
      },
      {
        "command": "rtl.openPreviewToSide",
        "title": "Open Preview to the Side",
        "category": "RTL",
        "icon": "$(open-preview)"
      },
      {
        "command": "rtl.refreshPreview",
        "title": "Refresh Preview",
        "category": "RTL"
      },
      {
        "command": "rtl.selectSampleData",
        "title": "Select Sample Data",
        "category": "RTL"
      },
      {
        "command": "rtl.editSampleData",
        "title": "Edit Sample Data",
        "category": "RTL"
      },
      {
        "command": "rtl.editAssignments",
        "title": "Edit Assignments",
        "category": "RTL"
      },
      {
        "command": "rtl.newTemplate",
        "title": "New Template",
        "category": "RTL",
        "icon": "$(new-file)"
      },
      {
        "command": "rtl.duplicateTemplate",
        "title": "Duplicate Template",
        "category": "RTL"
      },
      {
        "command": "rtl.restartServer",
        "title": "Restart Language Server",
        "category": "RTL"
      }
    ]
  }
}
```

### Keybindings

| Shortcut | Command | When |
|----------|---------|------|
| `Ctrl+Shift+V` | `rtl.openPreview` | `editorLangId == rtl` |
| `Ctrl+K V` | `rtl.openPreviewToSide` | `editorLangId == rtl` |
| `Ctrl+Shift+R` | `rtl.refreshPreview` | `editorLangId == rtl` |

**Note:** Standard VS Code shortcuts work automatically:
- `Ctrl+S` - Save
- `Ctrl+Space` - Trigger autocomplete
- `F8` / `Shift+F8` - Navigate problems
- `Ctrl+G` - Go to line
- `Ctrl+F` / `Ctrl+H` - Find / Replace

---

## 8. Configuration

### Extension Settings (contributes.configuration)

```json
{
  "contributes": {
    "configuration": {
      "title": "RTL - Receipt Template Language",
      "properties": {
        "rtl.templatesDirectory": {
          "type": "string",
          "default": "./templates",
          "description": "Path to templates directory"
        },
        "rtl.samplesDirectory": {
          "type": "string",
          "default": "./templates/samples",
          "description": "Path to sample data directory"
        },
        "rtl.defaultPrinterProfile": {
          "type": "string",
          "enum": ["tm-t30iii", "tm-u220ii"],
          "default": "tm-t30iii",
          "description": "Default printer profile for preview"
        },
        "rtl.preview.debounceMs": {
          "type": "number",
          "default": 300,
          "description": "Delay before updating preview after changes"
        },
        "rtl.server.path": {
          "type": "string",
          "default": "",
          "description": "Path to language server executable (uses bundled server if empty)"
        },
        "rtl.trace.server": {
          "type": "string",
          "enum": ["off", "messages", "verbose"],
          "default": "off",
          "description": "Traces communication between VS Code and the language server"
        }
      }
    }
  }
}
```

### Workspace Settings Example

```json
// .vscode/settings.json
{
  "rtl.templatesDirectory": "./templates",
  "rtl.samplesDirectory": "./templates/samples",
  "rtl.defaultPrinterProfile": "tm-t30iii"
}
```

---

## 9. Dependencies

### 9.1 Language Server (C#)

**Inspectron.Epson.Templates.LanguageServer.csproj:**

```xml
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>true</SelfContained>
  </PropertyGroup>

  <ItemGroup>
    <!-- OmniSharp Language Server Protocol -->
    <PackageReference Include="OmniSharp.Extensions.LanguageServer" Version="0.19.*" />
    <PackageReference Include="OmniSharp.Extensions.LanguageProtocol" Version="0.19.*" />

    <!-- Project Reference to Phase 1 -->
    <ProjectReference Include="../Inspectron.Epson.Templates/Inspectron.Epson.Templates.csproj" />
  </ItemGroup>
</Project>
```

### 9.2 VS Code Extension (TypeScript)

**package.json:**

```json
{
  "name": "vscode-rtl",
  "displayName": "RTL - Receipt Template Language",
  "description": "Syntax highlighting, autocomplete, and preview for Receipt Template Language",
  "version": "1.0.0",
  "publisher": "inspectron",
  "engines": {
    "vscode": "^1.85.0"
  },
  "categories": ["Programming Languages", "Formatters"],
  "activationEvents": [
    "onLanguage:rtl",
    "workspaceContains:**/*.template"
  ],
  "main": "./dist/extension.js",
  "dependencies": {
    "vscode-languageclient": "^9.0.0"
  },
  "devDependencies": {
    "@types/vscode": "^1.85.0",
    "@types/node": "^20.x",
    "typescript": "^5.3.0",
    "webpack": "^5.x",
    "webpack-cli": "^5.x",
    "ts-loader": "^9.x",
    "@vscode/test-electron": "^2.x"
  },
  "scripts": {
    "vscode:prepublish": "npm run package && npm run build-server",
    "compile": "webpack",
    "watch": "webpack --watch",
    "package": "webpack --mode production --devtool hidden-source-map",
    "build-server": "dotnet publish ../Inspectron.Epson.Templates.LanguageServer -c Release -o ./server",
    "test": "node ./out/test/runTest.js"
  }
}
```

---

## 10. Testing

### 10.1 Language Server Tests (C# - xUnit)

```
Inspectron.Epson.Templates.LanguageServer.Tests/
├── Handlers/
│   ├── SemanticTokensHandlerTests.cs
│   ├── CompletionHandlerTests.cs
│   ├── DiagnosticHandlerTests.cs
│   └── HoverHandlerTests.cs
├── Services/
│   ├── CompletionServiceTests.cs
│   ├── SchemaServiceTests.cs
│   ├── ScopeTrackerTests.cs
│   └── SemanticTokenServiceTests.cs
└── Fixtures/
    ├── valid-template.template
    ├── invalid-template.template
    └── sample-data.json
```

### Test Cases

**1. Semantic Tokens Tests**
- Tokenize style markers `#bold,center#` → decorator + string
- Tokenize bindings `{property.path}` → variable + property
- Tokenize control flow → keyword
- Tokenize separators → comment
- Tokenize row syntax `@row 60:40` → keyword + number

**2. Completion Tests**
- Complete styles after `#`
- Complete properties after `{`
- Complete child properties after `.`
- Complete format specifiers after `:`
- Complete loop variables in scope
- Complete loop metadata (`_index`, `_first`, etc.)

**3. Diagnostics Tests**
- Error on unclosed `@foreach`
- Error on unclosed `{` binding
- Warning on unused loop variable
- Error on invalid style name

**4. Schema Tests**
- Extract schema from nested JSON
- Resolve property paths
- Handle arrays correctly

**5. Scope Tracker Tests**
- Track single @foreach scope
- Track nested @foreach scopes
- Close scope after @end

### 10.2 VS Code Extension Tests (TypeScript)

```
vscode-rtl/test/
├── extension.test.ts         # Extension activation tests
├── languageClient.test.ts    # LSP client connection tests
└── fixtures/
    └── test-workspace/
        ├── test.template
        └── test.json
```

### Manual Test Checklist

- [ ] Install extension in VS Code
- [ ] Open `.template` file - syntax highlighting works (semantic tokens)
- [ ] Type `@` at line start - autocomplete shows control flow
- [ ] Type `{` - autocomplete shows data properties
- [ ] Type `{item.` inside foreach - autocomplete shows item properties + `_index`, `_first`, etc.
- [ ] Add syntax error - red squiggle appears, Problems panel updates
- [ ] Fix error - squiggle disappears
- [ ] Hover over style - shows description
- [ ] Hover over binding - shows type from schema
- [ ] Open preview (`Ctrl+Shift+V`) - receipt preview appears
- [ ] Edit template - preview updates live
- [ ] Change printer in preview - width adjusts
- [ ] Open assignments editor - table displays correctly
- [ ] Add/edit/delete assignment - saves to JSON
- [ ] Restart Language Server command works

---

## 11. Distribution

### Build Process

```bash
# 1. Build language server for all platforms
dotnet publish Inspectron.Epson.Templates.LanguageServer -c Release -r win-x64 -o vscode-rtl/server/win-x64
dotnet publish Inspectron.Epson.Templates.LanguageServer -c Release -r linux-x64 -o vscode-rtl/server/linux-x64
dotnet publish Inspectron.Epson.Templates.LanguageServer -c Release -r osx-x64 -o vscode-rtl/server/osx-x64

# 2. Build VS Code extension
cd vscode-rtl
npm install
npm run package

# 3. Create VSIX
npx @vscode/vsce package

# Creates: vscode-rtl-1.0.0.vsix
```

### VSIX Contents

```
vscode-rtl-1.0.0.vsix
├── extension/
│   ├── dist/
│   │   └── extension.js        # Bundled TypeScript
│   ├── server/
│   │   ├── win-x64/           # Windows language server
│   │   ├── linux-x64/         # Linux language server
│   │   └── osx-x64/           # macOS language server
│   ├── media/
│   ├── snippets/
│   ├── language-configuration.json
│   └── package.json
└── [Content_Types].xml
```

### Installation Options

1. **Local VSIX install:**
   ```bash
   code --install-extension vscode-rtl-1.0.0.vsix
   ```

2. **VS Code Marketplace:** (optional, for public distribution)
   ```bash
   npx @vscode/vsce publish
   ```

3. **Manual installation:**
   - Copy extracted folder to:
     - Windows: `%USERPROFILE%\.vscode\extensions\`
     - macOS/Linux: `~/.vscode/extensions/`

---

## 12. Timeline

### Phase 1: Core Engine (Foundation) - C#
1. Project setup and structure
2. Lexer implementation + tests
3. Parser implementation + tests
4. Interpreter implementation + tests
5. Configuration and resolver
6. File storage
7. Factory integration
8. Default templates
9. Integration testing
10. Migration and deployment

### Phase 2: VS Code Extension + Language Server (After Phase 1 Complete)

**Language Server (C#):**
1. Project setup with OmniSharp LSP
2. Server initialization and stdio transport
3. TextDocumentSyncHandler (open/change/close)
4. SemanticTokensHandler (syntax highlighting)
5. DiagnosticService + PublishDiagnostics
6. FoldingRangeHandler
7. HoverHandler
8. CompletionHandler - styles and control flow
9. SchemaService and ScopeTracker
10. CompletionHandler - data bindings with scope awareness
11. Custom request: rtl/renderPreview
12. Custom request: rtl/getSchema, rtl/listSamples
13. Unit tests

**VS Code Extension (TypeScript):**
14. Extension project setup (package.json, webpack)
15. Language client setup and server spawning
16. Language registration and configuration
17. Preview webview - calls rtl/renderPreview
18. Preview webview - printer selector, live updates
19. Tree views (templates, samples) - optional
20. Assignments editor webview - optional
21. Code snippets
22. Integration testing
23. Multi-platform build and VSIX packaging
24. Documentation and distribution
