@leadertechie/md2html 0.1.0-alpha.18 → 0.1.0-alpha.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -189,9 +189,94 @@ Additional safety with `maxRecursionDepth` (default: 100) to prevent stack overf
189
189
 
190
190
  ## Architecture (v2)
191
191
 
192
- ### Strategy Pattern Token Handlers
192
+ The pipeline is built from modular stages, each with a clear design pattern and single responsibility:
193
193
 
194
- The parser uses a **strategy pattern** with a `TokenHandlerRegistry`. Each marked token type has its own handler class:
194
+ ```
195
+ Markdown String
196
+
197
+
198
+ ┌──────────────────────────┐
199
+ │ 1. Preprocessor Chain │ Chain of Responsibility
200
+ │ (preprocessor.ts) │ Transforms raw markdown before lexing
201
+ │ • ContainerBlock │ (e.g., ::: containers → HTML comments)
202
+ └──────────┬───────────────┘
203
+
204
+
205
+ ┌──────────────────────────┐
206
+ │ 2. marked.lexer() │ Third-party lexer
207
+ └──────────┬───────────────┘
208
+
209
+
210
+ ┌──────────────────────────┐
211
+ │ 3. Token Postprocessor │ Chain of Responsibility
212
+ │ (token-postprocessor │ Restructures flat tokens → nested tree
213
+ │ .ts) │ (e.g., comments → containerBlock)
214
+ │ • ContainerBlock │
215
+ └──────────┬───────────────┘
216
+
217
+
218
+ ┌──────────────────────────┐
219
+ │ 4. Token Handlers │ Strategy Pattern
220
+ │ (handlers/) │ Each marked token type has a dedicated
221
+ │ • TokenHandlerRegistry│ handler, registered by type name.
222
+ │ • CatchAllHandler │ Extensible at runtime via registry.
223
+ └──────────┬───────────────┘
224
+
225
+
226
+ ContentNode[]
227
+ (AST)
228
+
229
+
230
+ ┌──────────────────────────┐
231
+ │ 5. Renderer │ Strategy Pattern
232
+ │ (renderer-strategies │ Each ContentNode type has its own
233
+ │ .ts / lit-strategies │ render strategy — choose between:
234
+ │ .ts) │ • HTMLRenderer (plain HTML strings)
235
+ │ • NodeRendererStrategy│ • LitRenderer (Lit TemplateResult)
236
+ │ • LitNodeRendererStrat│
237
+ └──────────────────────────┘
238
+ ```
239
+
240
+ ### 1. Preprocessing (`preprocessor.ts`)
241
+
242
+ The `CompositePreprocessor` chains `Preprocessor` transforms that run on raw markdown **before** lexing. Built-in:
243
+
244
+ - **`ContainerBlockPreprocessor`** — converts `:::tag#id.class` fences to `<!-- md-container:... -->` HTML comment markers, so `marked` preserves them without affecting inner markdown parsing
245
+
246
+ The chain is extensible:
247
+
248
+ ```typescript
249
+ import { MarkdownParser, Preprocessor } from '@leadertechie/md2html';
250
+
251
+ class EmojiPreprocessor implements Preprocessor {
252
+ readonly name = 'emoji';
253
+ process(markdown: string): string {
254
+ return markdown.replace(':smile:', '😊');
255
+ }
256
+ }
257
+
258
+ const parser = new MarkdownParser();
259
+ parser.preprocessors.add(new EmojiPreprocessor());
260
+ ```
261
+
262
+ ### 2. Token Postprocessing (`token-postprocessor.ts`)
263
+
264
+ The `CompositeTokenPostprocessor` chains `TokenPostprocessor` transforms that run on the flat token array **after** lexing. Built-in:
265
+
266
+ - **`ContainerBlockPostprocessor`** — collapses `<!-- md-container:... -->` / `<!-- /md-container -->` markers into nested `containerBlock` tokens with proper parent-child structure (handles arbitrary nesting depth)
267
+
268
+ Custom postprocessors:
269
+
270
+ ```typescript
271
+ parser.postprocessors.add({
272
+ name: 'filter-unwanted',
273
+ process: (tokens) => tokens.filter(t => (t as any).type !== 'html')
274
+ });
275
+ ```
276
+
277
+ ### 3. Token Handling — Strategy Pattern (`handlers/`)
278
+
279
+ Each marked token type has its own `TokenHandler` class, registered in the `TokenHandlerRegistry`:
195
280
 
196
281
  ```
197
282
  src/handlers/
@@ -205,6 +290,10 @@ src/handlers/
205
290
  ├── hr-handler.ts # <hr>
206
291
  ├── blockquote-handler.ts # <blockquote>
207
292
  ├── html-handler.ts # raw HTML passthrough
293
+ ├── link-handler.ts # <a>
294
+ ├── frontmatter-handler.ts# YAML frontmatter metadata
295
+ ├── container-block- # ::: container blocks
296
+ │ handler.ts
208
297
  └── catchall-handler.ts # fallback for unregistered types
209
298
  ```
210
299
 
@@ -253,6 +342,67 @@ const parser = new MarkdownParser({
253
342
  });
254
343
  ```
255
344
 
345
+ ### 4. Rendering — Strategy Pattern (`renderer-strategies.ts`, `lit-strategies.ts`)
346
+
347
+ The AST renderers use the same Strategy + Registry pattern as the token handlers:
348
+
349
+ - **`HTMLRenderer`** — produces plain HTML strings. Uses `NodeRendererStrategy` / `RendererStrategyRegistry` for each node type. Supports `classPrefix`, `addHeadingIds`, and `emitScopeAnchors` styling.
350
+ - **`LitRenderer`** — produces Lit `TemplateResult` objects. Uses `LitNodeRendererStrategy` / `LitStrategyRegistry`. Perfect for Lit web components.
351
+
352
+ Both registries are publicly accessible for customization:
353
+
354
+ ```typescript
355
+ import { HTMLRenderer, NodeRendererStrategy } from '@leadertechie/md2html';
356
+
357
+ const renderer = new HTMLRenderer({ classPrefix: 'my-' });
358
+
359
+ // Register a custom strategy
360
+ renderer.strategies.register({
361
+ type: 'custom',
362
+ render: (node, renderChild, ctx) => `<my-el>${node.content}</my-el>`
363
+ });
364
+ ```
365
+
366
+ The `LitRenderer.renderToHTMLString()` delegates to `HTMLRenderer` to avoid duplicating string rendering logic.
367
+
368
+ ### 5. Context Factory (`context-factory.ts`)
369
+
370
+ The `createParseContext()` pure function separates context construction from the parser class. It bridges parser services (image processing, slot resolution, HTML sanitization) to token handlers via the `ParserServices` interface. This makes the context testable in isolation and decouples handler logic from parser internals.
371
+
372
+ ### Source Map
373
+
374
+ ```
375
+ src/
376
+ ├── parser.ts # Orchestrator: coordinates pre/post-processing + token handling
377
+ ├── preprocessor.ts # Chain of Responsibility: markdown transforms before lexing
378
+ ├── token-postprocessor.ts # Chain of Responsibility: token transforms after lexing
379
+ ├── context-factory.ts # Factory: creates ParseContext for token handlers
380
+ ├── handlers/ # Strategy: per-token-type ContentNode producers
381
+ │ ├── types.ts
382
+ │ ├── registry.ts
383
+ │ ├── heading-handler.ts
384
+ │ ├── paragraph-handler.ts
385
+ │ ├── list-handler.ts
386
+ │ ├── image-handler.ts
387
+ │ ├── code-handler.ts
388
+ │ ├── hr-handler.ts
389
+ │ ├── blockquote-handler.ts
390
+ │ ├── html-handler.ts
391
+ │ ├── link-handler.ts
392
+ │ ├── frontmatter-handler.ts
393
+ │ ├── container-block-handler.ts
394
+ │ └── catchall-handler.ts
395
+ ├── renderer.ts # HTMLRenderer: transforms ContentNodes to plain HTML
396
+ ├── renderer-strategies.ts # Strategy: per-node-type HTML string renderers
397
+ ├── lit-renderer.ts # LitRenderer: transforms ContentNodes to Lit TemplateResult
398
+ ├── lit-strategies.ts # Strategy: per-node-type Lit TemplateResult renderers
399
+ ├── visitor.ts # Visitor: tree traversal utilities
400
+ ├── factory.ts # NodeFactory: ContentNode builder API
401
+ ├── pipeline.ts # Facade: high-level MarkdownPipeline API
402
+ ├── types.ts # Core types: ContentNode, MarkdownContent, configs
403
+ └── telemetry-init.ts # Shared logger initialization
404
+ ```
405
+
256
406
  ## License
257
407
 
258
408
  MIT
package/dist/index.d.ts CHANGED
@@ -1,3 +1,6 @@
1
+ import { LoggerInterface } from '@leadertechie/telemetry';
2
+ import { TemplateResult } from 'lit';
3
+
1
4
  /**
2
5
  * Handles 'blockquote' tokens.
3
6
  */
@@ -71,6 +74,42 @@ export declare function collectImages(root: ContentNode): Array<{
71
74
  alt: string;
72
75
  }>;
73
76
 
77
+ /**
78
+ * Sequentially applies multiple preprocessors to the markdown.
79
+ * Each preprocessor's output becomes the next one's input.
80
+ *
81
+ * This follows the Chain of Responsibility pattern — you can add
82
+ * new preprocessors without modifying existing code.
83
+ */
84
+ export declare class CompositePreprocessor implements Preprocessor {
85
+ readonly name = "composite";
86
+ private processors;
87
+ constructor(processors?: Preprocessor[]);
88
+ /** Add a preprocessor to the chain. Returns `this` for fluent API. */
89
+ add(processor: Preprocessor): this;
90
+ /** Remove a preprocessor by name. */
91
+ remove(name: string): void;
92
+ /** Get the list of registered preprocessors. */
93
+ getProcessors(): ReadonlyArray<Preprocessor>;
94
+ process(markdown: string): string;
95
+ }
96
+
97
+ /**
98
+ * Sequentially applies multiple postprocessors to the token array.
99
+ */
100
+ export declare class CompositeTokenPostprocessor implements TokenPostprocessor {
101
+ readonly name = "composite";
102
+ private processors;
103
+ constructor(processors?: TokenPostprocessor[]);
104
+ /** Add a postprocessor to the chain. Returns `this` for fluent API. */
105
+ add(processor: TokenPostprocessor): this;
106
+ /** Remove a postprocessor by name. */
107
+ remove(name: string): void;
108
+ /** Get the list of registered postprocessors. */
109
+ getProcessors(): ReadonlyArray<TokenPostprocessor>;
110
+ process(tokens: unknown[]): unknown[];
111
+ }
112
+
74
113
  /**
75
114
  * ContainerBlockHandler — handles custom :::tag#id.class container blocks.
76
115
  *
@@ -88,6 +127,39 @@ export declare class ContainerBlockHandler implements TokenHandler {
88
127
  handle(token: Record<string, unknown>, ctx: ParseContext): ContentNode | null;
89
128
  }
90
129
 
130
+ /**
131
+ * Collapses `<!-- md-container:... -->` and `<!-- /md-container -->` comment
132
+ * markers into nested `containerBlock` tokens with proper parent-child structure.
133
+ *
134
+ * Handles nesting depth up to any reasonable limit (depends on stack memory).
135
+ */
136
+ export declare class ContainerBlockPostprocessor implements TokenPostprocessor {
137
+ readonly name = "container-blocks";
138
+ process(tokens: unknown[]): unknown[];
139
+ }
140
+
141
+ /**
142
+ * Converts `:::tag#id.class` container syntax into HTML comment markers
143
+ * that marked will preserve as HTML tokens, without affecting markdown parsing
144
+ * of the inner content.
145
+ *
146
+ * Example:
147
+ * :::section#header
148
+ * # Heading inside container
149
+ * Some text
150
+ * :::
151
+ *
152
+ * Becomes:
153
+ * <!-- md-container:section#header -->
154
+ * # Heading inside container
155
+ * Some text
156
+ * <!-- /md-container -->
157
+ */
158
+ export declare class ContainerBlockPreprocessor implements Preprocessor {
159
+ readonly name = "container-blocks";
160
+ process(markdown: string): string;
161
+ }
162
+
91
163
  export declare interface ContentNode {
92
164
  type: ContentNodeType;
93
165
  content?: string;
@@ -105,6 +177,23 @@ export declare interface ContentNode {
105
177
 
106
178
  export declare type ContentNodeType = 'text' | 'heading' | 'paragraph' | 'list' | 'list-item' | 'image' | 'code' | 'container' | 'strong' | 'emphasis' | 'link';
107
179
 
180
+ /** Default postprocessor chain with the built-in container block support. */
181
+ export declare function createDefaultPostprocessor(): CompositeTokenPostprocessor;
182
+
183
+ /** Default preprocessor chain with the built-in container block support. */
184
+ export declare function createDefaultPreprocessor(): CompositePreprocessor;
185
+
186
+ /**
187
+ * Create a ParseContext from parser services.
188
+ *
189
+ * Each call creates a fresh context with its own metadata store,
190
+ * allowing for clean separation between recursive parse calls.
191
+ *
192
+ * The getters use closures to lazily access the parser's current state,
193
+ * so the context always reflects the latest configuration.
194
+ */
195
+ export declare function createParseContext(services: ParserServices): ParseContext;
196
+
108
197
  /** Default allowed HTML tags for preserveRawHTML mode */
109
198
  export declare const defaultAllowedHTMLTags: string[];
110
199
 
@@ -239,6 +328,118 @@ export declare class ListHandler implements TokenHandler {
239
328
  };
240
329
  }
241
330
 
331
+ export declare class LitCodeStrategy implements LitNodeRendererStrategy {
332
+ readonly type = "code";
333
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
334
+ }
335
+
336
+ export declare class LitContainerStrategy implements LitNodeRendererStrategy {
337
+ readonly type = "container";
338
+ render(node: ContentNode, renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
339
+ }
340
+
341
+ export declare class LitEmphasisStrategy implements LitNodeRendererStrategy {
342
+ readonly type = "emphasis";
343
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
344
+ }
345
+
346
+ export declare class LitFallbackStrategy implements LitNodeRendererStrategy {
347
+ readonly type = "*";
348
+ render(_node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
349
+ }
350
+
351
+ export declare class LitHeadingStrategy implements LitNodeRendererStrategy {
352
+ readonly type = "heading";
353
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
354
+ }
355
+
356
+ export declare class LitImageStrategy implements LitNodeRendererStrategy {
357
+ readonly type = "image";
358
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
359
+ }
360
+
361
+ export declare class LitLinkStrategy implements LitNodeRendererStrategy {
362
+ readonly type = "link";
363
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
364
+ }
365
+
366
+ export declare class LitListItemStrategy implements LitNodeRendererStrategy {
367
+ readonly type = "list-item";
368
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
369
+ }
370
+
371
+ export declare class LitListStrategy implements LitNodeRendererStrategy {
372
+ readonly type = "list";
373
+ render(node: ContentNode, renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
374
+ }
375
+
376
+ export declare interface LitNodeRendererStrategy {
377
+ /** The ContentNode type this strategy handles */
378
+ readonly type: string;
379
+ /** Render a node to a Lit TemplateResult */
380
+ render(node: ContentNode, renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
381
+ }
382
+
383
+ export declare class LitParagraphStrategy implements LitNodeRendererStrategy {
384
+ readonly type = "paragraph";
385
+ render(node: ContentNode, renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
386
+ }
387
+
388
+ export declare class LitRenderer {
389
+ private strategyRegistry;
390
+ /** Lazily created HTMLRenderer for string output, sharing the same config */
391
+ private htmlRenderer?;
392
+ constructor();
393
+ /** Access the strategy registry for customization. */
394
+ get strategies(): LitStrategyRegistry;
395
+ /**
396
+ * Render a single node to a Lit TemplateResult.
397
+ */
398
+ renderNode(node: ContentNode): TemplateResult;
399
+ /**
400
+ * Render an array of nodes to a single Lit TemplateResult.
401
+ */
402
+ renderNodes(nodes: ContentNode[]): TemplateResult;
403
+ /**
404
+ * Render nodes to a plain HTML string.
405
+ * Delegates to HTMLRenderer to avoid duplicating string rendering logic.
406
+ *
407
+ * Note: Uses default HTMLRenderer config (no classPrefix, scope anchors,
408
+ * or heading IDs). For full HTML rendering with those features,
409
+ * use HTMLRenderer directly.
410
+ */
411
+ renderToHTMLString(nodes: ContentNode[]): string;
412
+ }
413
+
414
+ /**
415
+ * Registry of Lit renderer strategies.
416
+ *
417
+ * Two-tier lookup:
418
+ * 1. Dedicated strategy by node type
419
+ * 2. Fallback to a catch-all strategy (default: renders empty)
420
+ */
421
+ export declare class LitStrategyRegistry {
422
+ private strategies;
423
+ private fallback;
424
+ constructor();
425
+ register(strategy: LitNodeRendererStrategy): void;
426
+ unregister(type: string): void;
427
+ get(type: string): LitNodeRendererStrategy;
428
+ has(type: string): boolean;
429
+ get types(): string[];
430
+ setFallback(strategy: LitNodeRendererStrategy): void;
431
+ }
432
+
433
+ export declare class LitStrongStrategy implements LitNodeRendererStrategy {
434
+ readonly type = "strong";
435
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
436
+ }
437
+
438
+ export declare class LitTextStrategy implements LitNodeRendererStrategy {
439
+ readonly type = "text";
440
+ render(node: ContentNode, _renderChild: (child: ContentNode) => TemplateResult): TemplateResult;
441
+ }
442
+
242
443
  export declare interface MarkdownContent {
243
444
  title: string;
244
445
  metadata?: Record<string, unknown>;
@@ -257,9 +458,16 @@ export declare class MarkdownParser {
257
458
  private allowedAttributes;
258
459
  private handlerRegistry;
259
460
  private onUnhandledToken?;
461
+ private log;
462
+ private preprocessor;
463
+ private postprocessor;
260
464
  constructor(options?: ParserOptions);
261
465
  /** Access the handler registry for customization. */
262
466
  get handlers(): TokenHandlerRegistry;
467
+ /** Access the preprocessor chain for customization. */
468
+ get preprocessors(): CompositePreprocessor;
469
+ /** Access the token postprocessor chain for customization. */
470
+ get postprocessors(): CompositeTokenPostprocessor;
263
471
  private processImagePath;
264
472
  private processInlineFormatting;
265
473
  private processSlots;
@@ -279,41 +487,16 @@ export declare class MarkdownParser {
279
487
  private filterTagAttributes;
280
488
  private processRawHTML;
281
489
  /**
282
- * Build the ParseContext that is passed to every token handler.
283
- * This is the bridge between the parser's private services and the handlers.
490
+ * Build a ParserServices object that bridges the parser's private methods
491
+ * to the ParseContext factory. This keeps context creation decoupled.
284
492
  */
285
- private createContext;
493
+ private buildServices;
286
494
  /**
287
495
  * Process an array of marked tokens into ContentNodes.
288
496
  * When depth === 0 (root), creates a shared context that accumulates metadata.
289
497
  * For recursive calls (depth > 0), creates a fresh context for each level.
290
498
  */
291
499
  private parseTokens;
292
- /**
293
- * Pre-process markdown: convert `:::tag#id.class` container syntax
294
- * into HTML comment markers that marked will preserve as html tokens,
295
- * but won't affect markdown parsing of the inner content.
296
- *
297
- * Example:
298
- * :::section#header
299
- * # Heading inside container
300
- * Some text
301
- * :::
302
- *
303
- * Becomes:
304
- * <!-- md-container:section#header -->
305
- * # Heading inside container
306
- * Some text
307
- * <!-- /md-container -->
308
- */
309
- private preprocessContainerBlocks;
310
- /**
311
- * Post-process marked tokens to collapse container block markers
312
- * into structured containerBlock tokens with proper nesting.
313
- *
314
- * This handles nesting depth up to maxRecursionDepth.
315
- */
316
- private postprocessTokens;
317
500
  parse(markdown: string, options?: ParseOptions): MarkdownContent;
318
501
  parseToNodes(markdown: string, options?: ParseOptions): ContentNode[];
319
502
  }
@@ -322,6 +505,7 @@ export declare class MarkdownPipeline {
322
505
  private parser;
323
506
  private renderer;
324
507
  private config;
508
+ private log;
325
509
  constructor(config?: PipelineConfigV2);
326
510
  parse(markdown: string): ContentNode[];
327
511
  parseWithMetadata(markdown: string): MarkdownContent;
@@ -448,6 +632,8 @@ export declare interface ParseOptions {
448
632
  }
449
633
 
450
634
  export declare interface ParserOptions {
635
+ /** Optional telemetry logger */
636
+ logger?: LoggerInterface;
451
637
  imagePathPrefix?: string;
452
638
  imageBaseUrl?: string;
453
639
  preserveRawHTML?: boolean;
@@ -474,6 +660,22 @@ export declare interface ParserOptions {
474
660
  onUnhandledToken?: (type: string, token: Record<string, unknown>) => void;
475
661
  }
476
662
 
663
+ /**
664
+ * Services needed by the ParseContext factory.
665
+ * This is the interface the parser exposes to context creation.
666
+ */
667
+ export declare interface ParserServices {
668
+ preserveRawHTML: boolean;
669
+ errorRecovery: 'throw' | 'warn' | 'silent';
670
+ maxRecursionDepth: number;
671
+ processImagePath(src: string): string;
672
+ processInlineFormatting(text: string): string;
673
+ processSlots(text: string): string;
674
+ processRawHTML(html: string): string;
675
+ parseTokens(tokens: unknown[], depth: number): ContentNode[];
676
+ onUnhandledToken?: (type: string, token: Record<string, unknown>) => void;
677
+ }
678
+
477
679
  export declare interface PipelineConfig {
478
680
  imagePathPrefix?: string;
479
681
  imageBaseUrl?: string;
@@ -485,6 +687,8 @@ export declare interface PipelineConfig {
485
687
  * v2: Extended PipelineConfig with raw HTML passthrough, slot hooks, and error recovery.
486
688
  */
487
689
  export declare interface PipelineConfigV2 extends PipelineConfig {
690
+ /** Optional telemetry logger for observability. Pass your own or a default console logger is used. */
691
+ logger?: LoggerInterface;
488
692
  styleOptions?: StyleConfigV2;
489
693
  /** Preserve raw HTML tags in markdown (img, style, div, span, etc.) (default: false) */
490
694
  preserveRawHTML?: boolean;
@@ -507,6 +711,24 @@ export declare interface PipelineConfigV2 extends PipelineConfig {
507
711
  allowedAttributes?: Record<string, string[]>;
508
712
  }
509
713
 
714
+ /**
715
+ * Markdown Preprocessor
716
+ *
717
+ * Single responsibility: Transform markdown text before it reaches the lexer.
718
+ * Currently handles `:::` container block syntax conversion to HTML comments.
719
+ *
720
+ * This is the first stage in the parsing pipeline.
721
+ *
722
+ * Extensibility: Additional preprocessors can be composed via the
723
+ * CompositePipeline pattern (see composite-pipeline.ts).
724
+ */
725
+ export declare interface Preprocessor {
726
+ /** Name identifier for logging/debugging */
727
+ readonly name: string;
728
+ /** Transform the markdown before lexing */
729
+ process(markdown: string): string;
730
+ }
731
+
510
732
  /**
511
733
  * Context passed to every render strategy, providing access to
512
734
  * shared rendering services and configuration.
@@ -612,6 +834,22 @@ export declare class TokenHandlerRegistry {
612
834
  getCatchAll(): TokenHandler;
613
835
  }
614
836
 
837
+ /**
838
+ * Token Postprocessor
839
+ *
840
+ * Single responsibility: Transform the flat array of marked tokens into
841
+ * a structured tree. Currently handles collapsing HTML comment markers
842
+ * (from container block preprocessing) into structured `containerBlock` tokens.
843
+ *
844
+ * This is the third stage in the parsing pipeline (after lexing).
845
+ */
846
+ export declare interface TokenPostprocessor {
847
+ /** Name identifier for logging/debugging */
848
+ readonly name: string;
849
+ /** Transform an array of tokens into an array of (possibly restructured) tokens */
850
+ process(tokens: unknown[]): unknown[];
851
+ }
852
+
615
853
  /**
616
854
  * Walk a ContentNode tree, applying a visitor at each node.
617
855
  * Returns a new tree (immutable) — does not mutate the original.