@xvml/cli 0.1.0

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.
@@ -0,0 +1,1083 @@
1
+ # XVML Specification
2
+
3
+ **Version:** 1.0
4
+ **Status:** Draft
5
+ **Project:** xvml
6
+
7
+ ---
8
+
9
+ ## Overview
10
+
11
+ XVML (eXpressive Visual Markup Language) is a plain-text format that renders as live UI. It sits between Markdown and HTML: human-readable like Markdown, but produces structured interactive interfaces like HTML. A XVML file is a sequence of commands; the renderer processes them top-to-bottom and emits a single self-contained HTML document.
12
+
13
+ ---
14
+
15
+ ## Core Syntax Rules
16
+
17
+ 1. Every command begins with `@` on its own line.
18
+ 2. The `@` token is always the first non-whitespace character on the line.
19
+ 3. Commands that contain other commands are opened with their keyword and closed with `@end`.
20
+ 4. String arguments are wrapped in double quotes: `"value"`.
21
+ 5. Modifier arguments are bare keywords (no quotes, no punctuation): `primary`, `required`, `horizontal`.
22
+ 6. Multiple arguments are space-separated on the same line as the command.
23
+ 7. Lines not starting with `@` inside a block are treated as raw text content for the enclosing command.
24
+ 8. Comments are lines beginning with `@#` — they are stripped before rendering.
25
+ 9. Blank lines are ignored.
26
+ 10. Command names are lowercase; unknown commands cause a render error.
27
+
28
+ ### Argument Types
29
+
30
+ | Type | Notation | Example |
31
+ |------------|---------------------|---------------------|
32
+ | `string` | `"..."` | `"Dashboard"` |
33
+ | `keyword` | bare word | `primary`, `large` |
34
+ | `number` | bare integer/float | `42`, `3.14` |
35
+ | `boolean` | `true` / `false` | `required true` |
36
+ | `path` | `"./path/to/file"` | `"./data.xvml"` |
37
+
38
+ ---
39
+
40
+ ## Command Reference
41
+
42
+ ---
43
+
44
+ ### Layout
45
+
46
+ ---
47
+
48
+ #### `@page`
49
+
50
+ Declares the root page container. Must be the first non-meta command in a file. There is exactly one `@page` per file.
51
+
52
+ **Syntax:**
53
+ ```
54
+ @page "Title" [theme-keyword]
55
+ ```
56
+
57
+ **Arguments:**
58
+ | Name | Type | Required | Description |
59
+ |---------|-----------|----------|-------------------------------------|
60
+ | title | `string` | yes | Document `<title>` and `<h1>` text |
61
+ | theme | `keyword` | no | Theme name: `light` (default), `dark`, `system` |
62
+
63
+ **HTML output:**
64
+ ```html
65
+ <!DOCTYPE html>
66
+ <html lang="en">
67
+ <head>
68
+ <meta charset="UTF-8">
69
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
70
+ <title>Title</title>
71
+ <style>/* inlined CSS */</style>
72
+ </head>
73
+ <body class="xvml-page xvml-theme-light">
74
+ <!-- child commands render here -->
75
+ </body>
76
+ </html>
77
+ ```
78
+
79
+ **Example:**
80
+ ```
81
+ @page "User Settings" dark
82
+ ```
83
+
84
+ ---
85
+
86
+ #### `@card`
87
+
88
+ A bordered, padded surface that groups related content. All UI content must live inside a `@card ... @end` block.
89
+
90
+ **Syntax:**
91
+ ```
92
+ @card ["Label"] [modifier...]
93
+ ...commands...
94
+ @end
95
+ ```
96
+
97
+ **Arguments:**
98
+ | Name | Type | Required | Description |
99
+ |----------|-----------|----------|----------------------------------------------|
100
+ | label | `string` | no | Optional heading rendered at top of card |
101
+ | modifier | `keyword` | no | `flat` (no shadow), `outlined`, `compact` |
102
+
103
+ **HTML output:**
104
+ ```html
105
+ <section class="xvml-card xvml-card--flat">
106
+ <h2 class="xvml-card__label">Label</h2>
107
+ <div class="xvml-card__body">
108
+ <!-- child commands -->
109
+ </div>
110
+ </section>
111
+ ```
112
+
113
+ **Example:**
114
+ ```
115
+ @card "Account Details" outlined
116
+ @field "Email" email required
117
+ @button "Save" primary
118
+ @end
119
+ ```
120
+
121
+ ---
122
+
123
+ #### `@end`
124
+
125
+ Closes the nearest open block command (`@card`, `@section`, `@cols`, `@stat-row`, `@list`, `@table`). Has no arguments and emits no HTML itself — it is a structural delimiter consumed by the parser.
126
+
127
+ **Syntax:**
128
+ ```
129
+ @end
130
+ ```
131
+
132
+ **Arguments:** none
133
+
134
+ **HTML output:** none (closes parent element)
135
+
136
+ **Example:**
137
+ ```
138
+ @card "Summary"
139
+ @text "All systems nominal."
140
+ @end
141
+ ```
142
+
143
+ ---
144
+
145
+ #### `@section`
146
+
147
+ A logical subdivision inside a `@card`. Adds a labelled sub-group with optional separator.
148
+
149
+ **Syntax:**
150
+ ```
151
+ @section "Label" [modifier...]
152
+ ...commands...
153
+ @end
154
+ ```
155
+
156
+ **Arguments:**
157
+ | Name | Type | Required | Description |
158
+ |----------|-----------|----------|--------------------------------------------|
159
+ | label | `string` | yes | Section heading text |
160
+ | modifier | `keyword` | no | `divided` (adds top border), `collapsible` |
161
+
162
+ **HTML output:**
163
+ ```html
164
+ <div class="xvml-section xvml-section--divided">
165
+ <h3 class="xvml-section__label">Label</h3>
166
+ <div class="xvml-section__body">
167
+ <!-- child commands -->
168
+ </div>
169
+ </div>
170
+ ```
171
+
172
+ **Example:**
173
+ ```
174
+ @card "Profile"
175
+ @section "Personal Info" divided
176
+ @field "First name" text
177
+ @field "Last name" text
178
+ @end
179
+ @end
180
+ ```
181
+
182
+ ---
183
+
184
+ #### `@layout`
185
+
186
+ Sets the layout algorithm for direct children of the enclosing block.
187
+
188
+ **Syntax:**
189
+ ```
190
+ @layout [mode]
191
+ ```
192
+
193
+ **Arguments:**
194
+ | Name | Type | Required | Description |
195
+ |------|-----------|----------|----------------------------------------------------------------|
196
+ | mode | `keyword` | no | `stack` (default, vertical), `inline`, `grid`, `center`, `fill` |
197
+
198
+ **HTML output:**
199
+ ```html
200
+ <div class="xvml-layout xvml-layout--inline">
201
+ <!-- subsequent sibling commands in the block -->
202
+ </div>
203
+ ```
204
+
205
+ **Example:**
206
+ ```
207
+ @card "Actions"
208
+ @layout inline
209
+ @button "Cancel"
210
+ @button "Confirm" primary
211
+ @end
212
+ ```
213
+
214
+ ---
215
+
216
+ #### `@cols`
217
+
218
+ Splits the enclosing area into equal-width columns. Children are distributed left-to-right.
219
+
220
+ **Syntax:**
221
+ ```
222
+ @cols [count]
223
+ ...commands...
224
+ @end
225
+ ```
226
+
227
+ **Arguments:**
228
+ | Name | Type | Required | Description |
229
+ |-------|----------|----------|----------------------------------------------|
230
+ | count | `number` | no | Column count, 1–6. Default: `2` |
231
+
232
+ **HTML output:**
233
+ ```html
234
+ <div class="xvml-cols xvml-cols--3">
235
+ <!-- child commands, each wrapped in a <div class="xvml-col"> -->
236
+ </div>
237
+ ```
238
+
239
+ **Example:**
240
+ ```
241
+ @cols 3
242
+ @stat "Revenue" "$12,400"
243
+ @stat "Users" "1,024"
244
+ @stat "Uptime" "99.9%"
245
+ @end
246
+ ```
247
+
248
+ ---
249
+
250
+ ### Content
251
+
252
+ ---
253
+
254
+ #### `@title`
255
+
256
+ Primary heading within a card or page section.
257
+
258
+ **Syntax:**
259
+ ```
260
+ @title "Text" [size]
261
+ ```
262
+
263
+ **Arguments:**
264
+ | Name | Type | Required | Description |
265
+ |------|-----------|----------|------------------------------------------|
266
+ | text | `string` | yes | Heading content |
267
+ | size | `keyword` | no | `xl`, `lg` (default), `md`, `sm` |
268
+
269
+ **HTML output:**
270
+ ```html
271
+ <h1 class="xvml-title xvml-title--lg">Text</h1>
272
+ ```
273
+
274
+ **Example:**
275
+ ```
276
+ @title "Welcome Back" xl
277
+ ```
278
+
279
+ ---
280
+
281
+ #### `@subtitle`
282
+
283
+ Secondary heading, visually subordinate to `@title`.
284
+
285
+ **Syntax:**
286
+ ```
287
+ @subtitle "Text" [muted]
288
+ ```
289
+
290
+ **Arguments:**
291
+ | Name | Type | Required | Description |
292
+ |-------|-----------|----------|-------------------------------------|
293
+ | text | `string` | yes | Subtitle content |
294
+ | muted | `keyword` | no | `muted` reduces text opacity |
295
+
296
+ **HTML output:**
297
+ ```html
298
+ <p class="xvml-subtitle xvml-subtitle--muted">Text</p>
299
+ ```
300
+
301
+ **Example:**
302
+ ```
303
+ @subtitle "Last login: 3 hours ago" muted
304
+ ```
305
+
306
+ ---
307
+
308
+ #### `@text`
309
+
310
+ Body paragraph text.
311
+
312
+ **Syntax:**
313
+ ```
314
+ @text "Content" [modifier]
315
+ ```
316
+
317
+ **Arguments:**
318
+ | Name | Type | Required | Description |
319
+ |----------|-----------|----------|----------------------------------------------------|
320
+ | content | `string` | yes | Paragraph content |
321
+ | modifier | `keyword` | no | `sm`, `muted`, `bold`, `mono`, `error`, `success` |
322
+
323
+ **HTML output:**
324
+ ```html
325
+ <p class="xvml-text xvml-text--muted">Content</p>
326
+ ```
327
+
328
+ **Example:**
329
+ ```
330
+ @text "Your changes have been saved." success
331
+ ```
332
+
333
+ ---
334
+
335
+ #### `@divider`
336
+
337
+ Horizontal separator rule.
338
+
339
+ **Syntax:**
340
+ ```
341
+ @divider [modifier]
342
+ ```
343
+
344
+ **Arguments:**
345
+ | Name | Type | Required | Description |
346
+ |----------|-----------|----------|--------------------------------------|
347
+ | modifier | `keyword` | no | `dashed`, `thick`, `spacious` |
348
+
349
+ **HTML output:**
350
+ ```html
351
+ <hr class="xvml-divider xvml-divider--dashed" />
352
+ ```
353
+
354
+ **Example:**
355
+ ```
356
+ @divider spacious
357
+ ```
358
+
359
+ ---
360
+
361
+ #### `@badge`
362
+
363
+ Small inline label used for status, category, or count indicators.
364
+
365
+ **Syntax:**
366
+ ```
367
+ @badge "Label" [variant]
368
+ ```
369
+
370
+ **Arguments:**
371
+ | Name | Type | Required | Description |
372
+ |---------|-----------|----------|-----------------------------------------------------------|
373
+ | label | `string` | yes | Badge text |
374
+ | variant | `keyword` | no | `neutral` (default), `success`, `warning`, `error`, `info` |
375
+
376
+ **HTML output:**
377
+ ```html
378
+ <span class="xvml-badge xvml-badge--success">Label</span>
379
+ ```
380
+
381
+ **Example:**
382
+ ```
383
+ @badge "Active" success
384
+ ```
385
+
386
+ ---
387
+
388
+ ### Form
389
+
390
+ ---
391
+
392
+ #### `@field`
393
+
394
+ A labelled text input.
395
+
396
+ **Syntax:**
397
+ ```
398
+ @field "Label" [type] [required] [placeholder "..."] [value "..."]
399
+ ```
400
+
401
+ **Arguments:**
402
+ | Name | Type | Required | Description |
403
+ |-------------|-----------|----------|-------------------------------------------------------------------------------------|
404
+ | label | `string` | yes | Visible label text |
405
+ | type | `keyword` | no | `text` (default), `email`, `password`, `number`, `tel`, `url`, `date`, `textarea` |
406
+ | required | `keyword` | no | Marks field as required; adds `required` attribute |
407
+ | placeholder | `string` | no | Placeholder text |
408
+ | value | `string` | no | Default value |
409
+
410
+ **HTML output:**
411
+ ```html
412
+ <div class="xvml-field">
413
+ <label class="xvml-field__label" for="xvml-field-0">Label</label>
414
+ <input
415
+ id="xvml-field-0"
416
+ class="xvml-field__input"
417
+ type="email"
418
+ placeholder="you@example.com"
419
+ required
420
+ />
421
+ </div>
422
+ ```
423
+
424
+ **Example:**
425
+ ```
426
+ @field "Email address" email required placeholder "you@example.com"
427
+ ```
428
+
429
+ ---
430
+
431
+ #### `@button`
432
+
433
+ An interactive button element.
434
+
435
+ **Syntax:**
436
+ ```
437
+ @button "Label" [variant] [size] [disabled]
438
+ ```
439
+
440
+ **Arguments:**
441
+ | Name | Type | Required | Description |
442
+ |----------|-----------|----------|----------------------------------------------------------|
443
+ | label | `string` | yes | Button text |
444
+ | variant | `keyword` | no | `default`, `primary`, `danger`, `ghost`, `link` |
445
+ | size | `keyword` | no | `sm`, `md` (default), `lg` |
446
+ | disabled | `keyword` | no | Renders button in disabled state |
447
+
448
+ **HTML output:**
449
+ ```html
450
+ <button class="xvml-button xvml-button--primary xvml-button--lg" type="button">
451
+ Label
452
+ </button>
453
+ ```
454
+
455
+ **Example:**
456
+ ```
457
+ @button "Delete Account" danger lg
458
+ ```
459
+
460
+ ---
461
+
462
+ #### `@checkbox`
463
+
464
+ A labelled checkbox input.
465
+
466
+ **Syntax:**
467
+ ```
468
+ @checkbox "Label" [checked] [disabled]
469
+ ```
470
+
471
+ **Arguments:**
472
+ | Name | Type | Required | Description |
473
+ |----------|-----------| ---------|------------------------------------|
474
+ | label | `string` | yes | Checkbox label text |
475
+ | checked | `keyword` | no | Renders pre-checked |
476
+ | disabled | `keyword` | no | Renders in disabled state |
477
+
478
+ **HTML output:**
479
+ ```html
480
+ <label class="xvml-checkbox">
481
+ <input class="xvml-checkbox__input" type="checkbox" checked />
482
+ <span class="xvml-checkbox__label">Label</span>
483
+ </label>
484
+ ```
485
+
486
+ **Example:**
487
+ ```
488
+ @checkbox "Send me product updates" checked
489
+ ```
490
+
491
+ ---
492
+
493
+ #### `@select`
494
+
495
+ A dropdown select input. Options are listed as quoted strings after the command.
496
+
497
+ **Syntax:**
498
+ ```
499
+ @select "Label" [required] "Option 1" "Option 2" ...
500
+ ```
501
+
502
+ **Arguments:**
503
+ | Name | Type | Required | Description |
504
+ |----------|-----------|----------|-----------------------------------------|
505
+ | label | `string` | yes | Visible label text |
506
+ | required | `keyword` | no | Adds `required` attribute |
507
+ | options | `string…` | yes | One or more option strings |
508
+
509
+ **HTML output:**
510
+ ```html
511
+ <div class="xvml-select">
512
+ <label class="xvml-select__label" for="xvml-select-0">Label</label>
513
+ <select id="xvml-select-0" class="xvml-select__input" required>
514
+ <option value="option-1">Option 1</option>
515
+ <option value="option-2">Option 2</option>
516
+ </select>
517
+ </div>
518
+ ```
519
+
520
+ **Example:**
521
+ ```
522
+ @select "Country" required "United States" "Canada" "United Kingdom" "Other"
523
+ ```
524
+
525
+ ---
526
+
527
+ #### `@link`
528
+
529
+ An anchor element.
530
+
531
+ **Syntax:**
532
+ ```
533
+ @link "Label" "href" [target]
534
+ ```
535
+
536
+ **Arguments:**
537
+ | Name | Type | Required | Description |
538
+ |--------|-----------|----------|----------------------------------------------------|
539
+ | label | `string` | yes | Link text |
540
+ | href | `string` | yes | URL or anchor path |
541
+ | target | `keyword` | no | `blank` renders `target="_blank" rel="noreferrer"` |
542
+
543
+ **HTML output:**
544
+ ```html
545
+ <a class="xvml-link" href="https://example.com" target="_blank" rel="noreferrer">
546
+ Label
547
+ </a>
548
+ ```
549
+
550
+ **Example:**
551
+ ```
552
+ @link "View documentation" "https://docs.example.com" blank
553
+ ```
554
+
555
+ ---
556
+
557
+ ### Data
558
+
559
+ ---
560
+
561
+ #### `@table`
562
+
563
+ A data table with a header row. Columns are defined by the first `row` call; subsequent rows supply values.
564
+
565
+ **Syntax:**
566
+ ```
567
+ @table [modifier]
568
+ @row "Col 1" "Col 2" "Col 3"
569
+ @row "Val A" "Val B" "Val C"
570
+ @end
571
+ ```
572
+
573
+ Sub-command `@row` is only valid inside `@table`. First `@row` becomes `<thead>`, subsequent rows become `<tbody>` rows.
574
+
575
+ **Arguments (table):**
576
+ | Name | Type | Required | Description |
577
+ |----------|-----------|----------|------------------------------------|
578
+ | modifier | `keyword` | no | `striped`, `compact`, `bordered` |
579
+
580
+ **Arguments (@row):**
581
+ | Name | Type | Required | Description |
582
+ |--------|-----------|----------|-----------------------|
583
+ | cells | `string…` | yes | Cell values |
584
+
585
+ **HTML output:**
586
+ ```html
587
+ <div class="xvml-table-wrapper">
588
+ <table class="xvml-table xvml-table--striped">
589
+ <thead>
590
+ <tr><th>Col 1</th><th>Col 2</th><th>Col 3</th></tr>
591
+ </thead>
592
+ <tbody>
593
+ <tr><td>Val A</td><td>Val B</td><td>Val C</td></tr>
594
+ </tbody>
595
+ </table>
596
+ </div>
597
+ ```
598
+
599
+ **Example:**
600
+ ```
601
+ @table striped
602
+ @row "Name" "Role" "Status"
603
+ @row "Alice" "Admin" "Active"
604
+ @row "Bob" "Viewer" "Inactive"
605
+ @end
606
+ ```
607
+
608
+ ---
609
+
610
+ #### `@stat`
611
+
612
+ A single key-value statistic display.
613
+
614
+ **Syntax:**
615
+ ```
616
+ @stat "Label" "Value" [trend]
617
+ ```
618
+
619
+ **Arguments:**
620
+ | Name | Type | Required | Description |
621
+ |-------|-----------|----------|------------------------------------------------|
622
+ | label | `string` | yes | Metric name |
623
+ | value | `string` | yes | Metric value |
624
+ | trend | `keyword` | no | `up`, `down`, `neutral` — renders a trend icon |
625
+
626
+ **HTML output:**
627
+ ```html
628
+ <div class="xvml-stat">
629
+ <span class="xvml-stat__label">Label</span>
630
+ <span class="xvml-stat__value">Value</span>
631
+ <span class="xvml-stat__trend xvml-stat__trend--up">↑</span>
632
+ </div>
633
+ ```
634
+
635
+ **Example:**
636
+ ```
637
+ @stat "Monthly Revenue" "$48,200" up
638
+ ```
639
+
640
+ ---
641
+
642
+ #### `@stat-row`
643
+
644
+ A horizontal group of `@stat` items with equal spacing. Shorthand for `@cols` containing stats.
645
+
646
+ **Syntax:**
647
+ ```
648
+ @stat-row
649
+ @stat "Label" "Value"
650
+ @stat "Label" "Value"
651
+ @end
652
+ ```
653
+
654
+ **Arguments:** none on `@stat-row`; child `@stat` commands follow their own syntax.
655
+
656
+ **HTML output:**
657
+ ```html
658
+ <div class="xvml-stat-row">
659
+ <div class="xvml-stat">...</div>
660
+ <div class="xvml-stat">...</div>
661
+ </div>
662
+ ```
663
+
664
+ **Example:**
665
+ ```
666
+ @stat-row
667
+ @stat "Users" "1,024" up
668
+ @stat "Sessions" "4,891" up
669
+ @stat "Bounce Rate" "34%" down
670
+ @end
671
+ ```
672
+
673
+ ---
674
+
675
+ #### `@progress`
676
+
677
+ A labelled progress bar.
678
+
679
+ **Syntax:**
680
+ ```
681
+ @progress "Label" [value] [max] [variant]
682
+ ```
683
+
684
+ **Arguments:**
685
+ | Name | Type | Required | Description |
686
+ |---------|-----------|----------|-------------------------------------------------------|
687
+ | label | `string` | yes | Visible label |
688
+ | value | `number` | no | Current value, 0–max. Default: `0` |
689
+ | max | `number` | no | Maximum value. Default: `100` |
690
+ | variant | `keyword` | no | `default`, `success`, `warning`, `error` |
691
+
692
+ **HTML output:**
693
+ ```html
694
+ <div class="xvml-progress">
695
+ <div class="xvml-progress__header">
696
+ <span class="xvml-progress__label">Label</span>
697
+ <span class="xvml-progress__value">72%</span>
698
+ </div>
699
+ <div class="xvml-progress__track">
700
+ <div class="xvml-progress__fill xvml-progress__fill--success" style="width:72%"></div>
701
+ </div>
702
+ </div>
703
+ ```
704
+
705
+ **Example:**
706
+ ```
707
+ @progress "Storage used" 72 100 warning
708
+ ```
709
+
710
+ ---
711
+
712
+ #### `@list`
713
+
714
+ An ordered or unordered list. Items are child `@item` commands.
715
+
716
+ **Syntax:**
717
+ ```
718
+ @list [modifier]
719
+ @item "Text"
720
+ @item "Text"
721
+ @end
722
+ ```
723
+
724
+ Sub-command `@item` is only valid inside `@list`.
725
+
726
+ **Arguments (list):**
727
+ | Name | Type | Required | Description |
728
+ |----------|-----------|----------|---------------------------------------|
729
+ | modifier | `keyword` | no | `ordered`, `unordered` (default), `check` |
730
+
731
+ **Arguments (@item):**
732
+ | Name | Type | Required | Description |
733
+ |-------|----------|----------|-----------------|
734
+ | text | `string` | yes | Item text |
735
+
736
+ **HTML output:**
737
+ ```html
738
+ <ul class="xvml-list xvml-list--check">
739
+ <li class="xvml-list__item">Text</li>
740
+ </ul>
741
+ ```
742
+
743
+ **Example:**
744
+ ```
745
+ @list check
746
+ @item "Enable two-factor authentication"
747
+ @item "Review active sessions"
748
+ @item "Download backup codes"
749
+ @end
750
+ ```
751
+
752
+ ---
753
+
754
+ ### Code
755
+
756
+ ---
757
+
758
+ #### `@codeblock`
759
+
760
+ A syntax-highlighted code block.
761
+
762
+ **Syntax:**
763
+ ```
764
+ @codeblock [language] ["filename"]
765
+ <raw code lines>
766
+ @end
767
+ ```
768
+
769
+ All lines between `@codeblock` and `@end` are treated as raw text (no `@` parsing).
770
+
771
+ **Arguments:**
772
+ | Name | Type | Required | Description |
773
+ |----------|-----------|----------|-----------------------------------------------|
774
+ | language | `keyword` | no | Language hint: `ts`, `js`, `json`, `bash`, `html`, `css`, `xvml`, etc. Default: `text` |
775
+ | filename | `string` | no | Optional filename label shown above block |
776
+
777
+ **HTML output:**
778
+ ```html
779
+ <div class="xvml-codeblock">
780
+ <div class="xvml-codeblock__header">
781
+ <span class="xvml-codeblock__lang">ts</span>
782
+ <span class="xvml-codeblock__filename">index.ts</span>
783
+ </div>
784
+ <pre class="xvml-codeblock__pre"><code class="xvml-codeblock__code language-ts">// code here</code></pre>
785
+ </div>
786
+ ```
787
+
788
+ **Example:**
789
+ ```
790
+ @codeblock ts "renderer.ts"
791
+ export function render(xvml: string): string {
792
+ return parse(xvml).toHTML();
793
+ }
794
+ @end
795
+ ```
796
+
797
+ ---
798
+
799
+ #### `@constraint`
800
+
801
+ Declares a named validation rule or system constraint. Rendered as a visually distinct rule block, useful for spec documents and configuration pages.
802
+
803
+ **Syntax:**
804
+ ```
805
+ @constraint "Name" "Description" [severity]
806
+ ```
807
+
808
+ **Arguments:**
809
+ | Name | Type | Required | Description |
810
+ |-------------|-----------|----------|------------------------------------------------|
811
+ | name | `string` | yes | Short constraint identifier |
812
+ | description | `string` | yes | Human-readable rule text |
813
+ | severity | `keyword` | no | `must` (default), `should`, `may` |
814
+
815
+ **HTML output:**
816
+ ```html
817
+ <div class="xvml-constraint xvml-constraint--must">
818
+ <span class="xvml-constraint__severity">MUST</span>
819
+ <span class="xvml-constraint__name">Name</span>
820
+ <p class="xvml-constraint__desc">Description</p>
821
+ </div>
822
+ ```
823
+
824
+ **Example:**
825
+ ```
826
+ @constraint "no-any" "TypeScript files must not use the any type." must
827
+ ```
828
+
829
+ ---
830
+
831
+ #### `@alert`
832
+
833
+ An inline alert banner for notices, warnings, and errors.
834
+
835
+ **Syntax:**
836
+ ```
837
+ @alert "Message" [variant]
838
+ ```
839
+
840
+ **Arguments:**
841
+ | Name | Type | Required | Description |
842
+ |---------|-----------|----------|------------------------------------------------------|
843
+ | message | `string` | yes | Alert text |
844
+ | variant | `keyword` | no | `info` (default), `success`, `warning`, `error` |
845
+
846
+ **HTML output:**
847
+ ```html
848
+ <div class="xvml-alert xvml-alert--warning" role="alert">
849
+ <span class="xvml-alert__icon">⚠</span>
850
+ <span class="xvml-alert__message">Message</span>
851
+ </div>
852
+ ```
853
+
854
+ **Example:**
855
+ ```
856
+ @alert "This action cannot be undone." error
857
+ ```
858
+
859
+ ---
860
+
861
+ ### Meta
862
+
863
+ ---
864
+
865
+ #### `@spec`
866
+
867
+ Declares the XVML spec version this file targets. Must appear before `@page` if present.
868
+
869
+ **Syntax:**
870
+ ```
871
+ @spec [version]
872
+ ```
873
+
874
+ **Arguments:**
875
+ | Name | Type | Required | Description |
876
+ |---------|----------|----------|------------------------------------|
877
+ | version | `number` | no | Spec version number. Default: `1` |
878
+
879
+ **HTML output:** none (parsed by renderer, stripped before output)
880
+
881
+ **Example:**
882
+ ```
883
+ @spec 1
884
+ ```
885
+
886
+ ---
887
+
888
+ #### `@file`
889
+
890
+ Documents the source file path for this XVML file. Used by the renderer to set output filename and by tooling for source maps.
891
+
892
+ **Syntax:**
893
+ ```
894
+ @file "path/to/file.xvml"
895
+ ```
896
+
897
+ **Arguments:**
898
+ | Name | Type | Required | Description |
899
+ |------|--------|----------|----------------------------------|
900
+ | path | `path` | yes | Relative path of this file |
901
+
902
+ **HTML output:** none (metadata only)
903
+
904
+ **Example:**
905
+ ```
906
+ @file "pages/settings.xvml"
907
+ ```
908
+
909
+ ---
910
+
911
+ #### `@meta`
912
+
913
+ Sets an arbitrary metadata key-value pair. Rendered into `<meta>` tags in the document `<head>`.
914
+
915
+ **Syntax:**
916
+ ```
917
+ @meta "key" "value"
918
+ ```
919
+
920
+ **Arguments:**
921
+ | Name | Type | Required | Description |
922
+ |-------|----------|----------|----------------------|
923
+ | key | `string` | yes | Meta tag `name` |
924
+ | value | `string` | yes | Meta tag `content` |
925
+
926
+ **HTML output:**
927
+ ```html
928
+ <meta name="key" content="value" />
929
+ ```
930
+
931
+ **Example:**
932
+ ```
933
+ @meta "description" "User account settings page"
934
+ ```
935
+
936
+ ---
937
+
938
+ #### `@import`
939
+
940
+ Inlines the content of another `.xvml` file at this position. Imports are resolved at render time; circular imports are a render error.
941
+
942
+ **Syntax:**
943
+ ```
944
+ @import "path/to/file.xvml"
945
+ ```
946
+
947
+ **Arguments:**
948
+ | Name | Type | Required | Description |
949
+ |------|--------|----------|---------------------------------------------------------|
950
+ | path | `path` | yes | Relative path to the `.xvml` file to inline |
951
+
952
+ **HTML output:** The fully rendered HTML of the imported file's body (without its outer `<html>` wrapper).
953
+
954
+ **Example:**
955
+ ```
956
+ @import "./components/navbar.xvml"
957
+ ```
958
+
959
+ ---
960
+
961
+ #### `@theme`
962
+
963
+ Defines theme variables. All values are inlined as CSS custom properties on `:root`.
964
+
965
+ **Syntax:**
966
+ ```
967
+ @theme "name"
968
+ "color-primary" "#6366f1"
969
+ "color-bg" "#ffffff"
970
+ @end
971
+ ```
972
+
973
+ Key-value pairs inside the block are bare `"key" "value"` lines (not commands).
974
+
975
+ **Arguments (theme):**
976
+ | Name | Type | Required | Description |
977
+ |------|----------|----------|----------------------------|
978
+ | name | `string` | yes | Theme identifier |
979
+
980
+ **HTML output:**
981
+ ```html
982
+ <style>
983
+ :root {
984
+ --xvml-color-primary: #6366f1;
985
+ --xvml-color-bg: #ffffff;
986
+ }
987
+ </style>
988
+ ```
989
+
990
+ **Example:**
991
+ ```
992
+ @theme "brand"
993
+ "color-primary" "#0f172a"
994
+ "color-accent" "#6366f1"
995
+ "font-body" "Inter, sans-serif"
996
+ @end
997
+ ```
998
+
999
+ ---
1000
+
1001
+ #### `@renderer`
1002
+
1003
+ Passes configuration flags to the renderer. Affects the output HTML but emits no visible content.
1004
+
1005
+ **Syntax:**
1006
+ ```
1007
+ @renderer [flag...]
1008
+ ```
1009
+
1010
+ **Arguments:**
1011
+ | Name | Type | Required | Description |
1012
+ |-------------|-----------|----------|--------------------------------------------------------------|
1013
+ | minify | `keyword` | no | Minify the HTML output |
1014
+ | no-scripts | `keyword` | no | Strip all inline JavaScript (static render only) |
1015
+ | rtl | `keyword` | no | Set `dir="rtl"` on `<body>` |
1016
+
1017
+ **HTML output:** none (modifies renderer pipeline)
1018
+
1019
+ **Example:**
1020
+ ```
1021
+ @renderer minify no-scripts
1022
+ ```
1023
+
1024
+ ---
1025
+
1026
+ ## Full File Example
1027
+
1028
+ ```xvml
1029
+ @spec 1
1030
+ @file "pages/dashboard.xvml"
1031
+ @meta "description" "Team dashboard overview"
1032
+
1033
+ @page "Team Dashboard" light
1034
+
1035
+ @card "Overview"
1036
+ @stat-row
1037
+ @stat "Members" "24" up
1038
+ @stat "Open Tasks" "11" neutral
1039
+ @stat "Completed" "89" up
1040
+ @end
1041
+ @end
1042
+
1043
+ @card "Recent Activity" compact
1044
+ @table striped
1045
+ @row "User" "Action" "Time"
1046
+ @row "Alice" "Deployed v2.3" "2m ago"
1047
+ @row "Bob" "Opened PR #44" "15m ago"
1048
+ @end
1049
+ @end
1050
+
1051
+ @card "Health"
1052
+ @progress "API uptime" 99 100 success
1053
+ @progress "Storage" 71 100 warning
1054
+ @end
1055
+ ```
1056
+
1057
+ ---
1058
+
1059
+ ## Reserved — Future
1060
+
1061
+ The following commands are defined by name only. They **must not** appear in XVML 1.0 files; the renderer will emit an error if encountered. They are reserved for a future reactive/scripting extension.
1062
+
1063
+ | Command | Intended purpose |
1064
+ |--------------|---------------------------------------------------------------------|
1065
+ | `@if` | Conditional rendering based on a bound variable |
1066
+ | `@each` | Loop over a data array, rendering children once per item |
1067
+ | `@bind` | Two-way binding between a form field and a named variable |
1068
+ | `@on:click` | Attach a click handler expression to the nearest element |
1069
+ | `@event` | Declare a named custom event that the page can emit |
1070
+ | `@agent` | Invoke an AI agent inline and render its structured output |
1071
+
1072
+ ---
1073
+
1074
+ ## Renderer Contract
1075
+
1076
+ A conforming XVML renderer must satisfy all of the following:
1077
+
1078
+ - Output a single `.html` file per `.xvml` source file.
1079
+ - All CSS and JavaScript must be inlined; no external URLs in rendered output.
1080
+ - Rendering must be **deterministic**: identical input produces byte-for-byte identical output.
1081
+ - Unknown commands must produce a render error, not silent fallback.
1082
+ - `@import` paths are resolved relative to the source file, not the working directory.
1083
+ - Output files are written to the `/docs` directory by default.