atdoc-core 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,992 @@
1
+ # @Doc Block Syntax Specification v1.4
2
+
3
+ > 🌐 Other languages: [Traditional Chinese](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/zh-tw/Block-Syntax-Specification.md) ・ [Simplified Chinese](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/zh-cn/Block-Syntax-Specification.md) ・ [Japanese (AI translation; may contain errors)](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/ja/Block-Syntax-Specification.md) ・ [Korean (AI translation; may contain errors)](https://github.com/WEDC-Studio-Official/AtDoc/blob/main/docs/ko/Block-Syntax-Specification.md)
4
+
5
+ ## 0. Table of Contents
6
+
7
+ * [1. Design Philosophy](#1-design-philosophy)
8
+ * [2. Document AST Structure](#2-document-ast-structure)
9
+ * [3. EBNF](#3-ebnf)
10
+ * [4. Shared Components](#4-shared-components)
11
+ * [5. Structural Blocks](#5-structural-blocks)
12
+ * [6. Container Blocks](#6-container-blocks)
13
+ * [7. Callout Blocks](#7-callout-blocks)
14
+ * [8. Widget Blocks](#8-widget-blocks)
15
+ * [9. Metadata](#9-metadata)
16
+ * [10. Core Principle](#10-core-principle)
17
+ * [11. Simplified Syntax Aliases](#11-simplified-syntax-aliases)
18
+
19
+ ---
20
+
21
+ ## 1. Design Philosophy
22
+
23
+ @Doc Block Syntax adopts:
24
+
25
+ > **Semantic First, Layout Later**
26
+
27
+ Block nodes describe:
28
+
29
+ > The semantics of the document (What)
30
+
31
+ rather than:
32
+
33
+ > The presentation (How)
34
+
35
+ Therefore @Doc does not provide:
36
+
37
+ * `@div`
38
+ * `@span`
39
+ * `@flex`
40
+ * `@grid`
41
+ * `@row`
42
+ * `@col`
43
+ * `@class`
44
+ * `@style`
45
+
46
+ The renderer can freely decide, based on the platform:
47
+
48
+ * HTML
49
+ * React
50
+ * PDF
51
+ * DOCX
52
+ * Discord
53
+ * Terminal
54
+ * Notion
55
+ * AI UI
56
+
57
+ ---
58
+
59
+ ## 2. Document AST Structure
60
+
61
+ ```text
62
+ Document AST
63
+
64
+ ├── Metadata
65
+ │ └── @meta
66
+
67
+ ├── Block Nodes
68
+ │ │
69
+ │ ├── Structural Blocks
70
+ │ │ ├── @heading (alias: @h)
71
+ │ │ ├── @paragraph (alias: @p)
72
+ │ │ ├── @quote
73
+ │ │ ├── @list
74
+ │ │ ├── @code
75
+ │ │ ├── @img
76
+ │ │ ├── @table
77
+ │ │ ├── @hr
78
+ │ │ └── @svg
79
+ │ │
80
+ │ ├── Container Blocks
81
+ │ │ ├── @details
82
+ │ │ └── @card
83
+ │ │
84
+ │ ├── Callout Blocks
85
+ │ │ ├── @note
86
+ │ │ ├── @tip
87
+ │ │ ├── @important
88
+ │ │ ├── @warning
89
+ │ │ └── @caution
90
+ │ │
91
+ │ └── Widget Blocks
92
+ │ ├── @tabs
93
+ │ ├── @tab
94
+ │ └── @mermaid
95
+
96
+ └── Inline Nodes
97
+
98
+ ├── Text Formatting
99
+ │ ├── @mark
100
+ │ ├── @color
101
+ │ ├── @bordered
102
+ │ ├── @bold (alias: @b)
103
+ │ ├── @italic (alias: @i)
104
+ │ ├── @underline (alias: @u)
105
+ │ ├── @del
106
+ │ └── @raw
107
+
108
+ ├── Semantic Inline
109
+ │ ├── @sup
110
+ │ ├── @sub
111
+ │ ├── @kbd
112
+ │ └── @link
113
+
114
+ ├── Footnotes
115
+ │ ├── @fn
116
+ │ └── @defn
117
+
118
+ └── Special Nodes
119
+ ├── @n
120
+ └── @@
121
+ ```
122
+
123
+ ---
124
+
125
+ ## 3. EBNF
126
+
127
+ ```ebnf
128
+ document =
129
+ [ metadata ],
130
+ { block-node } ;
131
+
132
+ block-node =
133
+ heading
134
+ | paragraph
135
+ | quote
136
+ | list
137
+ | code
138
+ | image
139
+ | table
140
+ | hr
141
+ | svg
142
+ | details
143
+ | card
144
+ | note
145
+ | tip
146
+ | important
147
+ | warning
148
+ | caution
149
+ | tabs
150
+ | mermaid ;
151
+
152
+ (* Note:
153
+ `tab` is not part of block-node.
154
+ It is child-node syntax exclusive to @tabs, and can only appear within
155
+ tabs-content — see "Widget-Specific Grammar: @tabs" below for details.
156
+ *)
157
+
158
+ metadata =
159
+ "@meta" , meta-content ;
160
+
161
+ (* meta-content is lexed the same way as block-content — the "[" / "]" pair
162
+ tokenizes normally, so an unregistered "@word" still falls back to plain
163
+ text per §6 Unknown Command Fallback — but Parser.ts is semantically
164
+ stricter here than for any other block node: it rejects every registered
165
+ node inside @meta, not just structural ones, not even @n or @raw. The
166
+ parser then splits the resulting text on newlines and the first "=" on
167
+ each line into key/value pairs and stores them directly on the AST node
168
+ (MetaNode.meta), rather than leaving that structuring to a later pass.
169
+ See Metadata.md §3/§6 for the full behavior and worked examples. *)
170
+ meta-content =
171
+ "[" ,
172
+ { text } ,
173
+ "]" ;
174
+
175
+ heading =
176
+ ( "@heading" | "@h" ) ,
177
+ [ "(" , level , ")" ] ,
178
+ block-content ;
179
+
180
+ paragraph =
181
+ ( "@paragraph" | "@p" ) , block-content ;
182
+
183
+ quote =
184
+ "@quote" , block-content ;
185
+
186
+ list =
187
+ "@list" ,
188
+ [ "(" , "ordered" , ")" ] ,
189
+ block-content ;
190
+
191
+ code =
192
+ "@code" ,
193
+ [ language ] ,
194
+ raw-block-content ;
195
+
196
+ image =
197
+ "@img" ,
198
+ "(" ,
199
+ image-option-list ,
200
+ ")" ,
201
+ [ styles ] ,
202
+ block-content ;
203
+
204
+ hr =
205
+ "@hr" ;
206
+
207
+ svg =
208
+ "@svg" ,
209
+ raw-block-content ;
210
+
211
+ details =
212
+ "@details" ,
213
+ [ title ] ,
214
+ [ styles ] ,
215
+ block-content ;
216
+
217
+ card =
218
+ "@card" ,
219
+ [ title ] ,
220
+ [ styles ] ,
221
+ block-content ;
222
+
223
+ note =
224
+ "@note" ,
225
+ [ title ] ,
226
+ [ styles ] ,
227
+ block-content ;
228
+
229
+ tip =
230
+ "@tip" ,
231
+ [ title ] ,
232
+ [ styles ] ,
233
+ block-content ;
234
+
235
+ important =
236
+ "@important" ,
237
+ [ title ] ,
238
+ [ styles ] ,
239
+ block-content ;
240
+
241
+ warning =
242
+ "@warning" ,
243
+ [ title ] ,
244
+ [ styles ] ,
245
+ block-content ;
246
+
247
+ caution =
248
+ "@caution" ,
249
+ [ title ] ,
250
+ [ styles ] ,
251
+ block-content ;
252
+
253
+ mermaid =
254
+ "@mermaid" ,
255
+ raw-block-content ;
256
+
257
+ (* ==========================================================================
258
+ Structural-Specific Grammar: @img
259
+
260
+ The parenthesized content of @img is not a single bare text, but a
261
+ comma-separated key=value option list (image-option-list), which is
262
+ extensible. If the first option omits the key, it defaults to src.
263
+
264
+ The `image` production (above) also has an independent, optional
265
+ [styles] after ")" — Image Style v1, which at the grammar level is
266
+ completely unrelated to image-option-list (it cannot be written inside
267
+ the parentheses); see the "Image Style v1" subsection below for its
268
+ semantics.
269
+ ========================================================================== *)
270
+
271
+ image-option-list =
272
+ image-option ,
273
+ { "," , image-option } ;
274
+
275
+ image-option =
276
+ src-option
277
+ | width-option
278
+ | height-option
279
+ | align-option
280
+ | radius-option
281
+ | border-option ;
282
+
283
+ src-option =
284
+ [ "src=" ] , url ;
285
+
286
+ width-option =
287
+ "width=" , integer ;
288
+
289
+ height-option =
290
+ "height=" , integer ;
291
+
292
+ align-option =
293
+ "align=" , ( "left" | "center" | "right" ) ;
294
+
295
+ radius-option =
296
+ "radius=" , text ;
297
+
298
+ border-option =
299
+ "border=" , text ;
300
+
301
+ url =
302
+ { text-char - "," - ")" } ;
303
+
304
+ (* ==========================================================================
305
+ Widget-Specific Grammar: @table
306
+
307
+ @table does not use the generic block-content; instead it has its own
308
+ dedicated structured syntax (Columns + Rows).
309
+ ========================================================================== *)
310
+
311
+ table =
312
+ "@table" , table-content ;
313
+
314
+ table-content =
315
+ "[" ,
316
+ cols ,
317
+ data ,
318
+ "]" ;
319
+
320
+ cols =
321
+ "@cols" ,
322
+ "[" ,
323
+ column-list ,
324
+ "]" ;
325
+
326
+ column-list =
327
+ cell ,
328
+ { "," , cell } ;
329
+
330
+ data =
331
+ "@data" ,
332
+ "[" ,
333
+ { row } ,
334
+ "]" ;
335
+
336
+ row =
337
+ "[" ,
338
+ cell ,
339
+ { "," , cell } ,
340
+ "]" ;
341
+
342
+ (* A cell isn't plain text only — it also allows a curated subset of
343
+ inline-node (cell-inline-node), the same shape @cols columns and @data
344
+ cells share. The authoritative allowlist lives in registry.ts's
345
+ isCellAllowedNode(), not this grammar — a node outside that set (e.g.
346
+ @card, @table, @details) MUST throw rather than being silently dropped,
347
+ per Strict Mode (Inline Syntax Specification §11). *)
348
+ cell =
349
+ { cell-inline-node | any-unicode-char - "," - "]" } ;
350
+
351
+ (* ==========================================================================
352
+ Widget-Specific Grammar: @tabs / @tab
353
+
354
+ @tab can only appear within @tabs' tabs-content; it does not belong to
355
+ the generic block-node set, and therefore cannot appear on its own at
356
+ the top level of a document or within any other block-content.
357
+ ========================================================================== *)
358
+
359
+ tabs =
360
+ "@tabs" , tabs-content ;
361
+
362
+ tabs-content =
363
+ "[" ,
364
+ { tab } ,
365
+ "]" ;
366
+
367
+ tab =
368
+ "@tab" ,
369
+ "(" ,
370
+ text ,
371
+ ")" ,
372
+ block-content ;
373
+ ```
374
+
375
+ ---
376
+
377
+ ## 4. Shared Components
378
+
379
+ ```ebnf
380
+ block-content =
381
+ "[" ,
382
+ { block-element } ,
383
+ "]" ;
384
+
385
+ block-element =
386
+ block-node
387
+ | inline-stream
388
+ | text ;
389
+
390
+ raw-block-content =
391
+ "[" ,
392
+ { any-unicode-char } ,
393
+ "]" ;
394
+
395
+ title =
396
+ "(" ,
397
+ text ,
398
+ ")" ;
399
+
400
+ language =
401
+ "(" ,
402
+ text ,
403
+ ")" ;
404
+
405
+ level =
406
+ "1"
407
+ | "2"
408
+ | "3"
409
+ | "4"
410
+ | "5"
411
+ | "6" ;
412
+
413
+ text =
414
+ { any-unicode-char } ;
415
+ ```
416
+
417
+ > The terminal definitions `integer`, `text-char`, etc. reuse the `integer`
418
+ > and `text-char` productions from Inline Spec §4 (Complete EBNF Grammar
419
+ > Definition); both documents share the same character-set definitions, so
420
+ > they are not repeated here.
421
+ >
422
+ > `styles` likewise reuses the `styles` production from Inline Spec §4
423
+ > (`"{" , { text-char - "}" } , "}"`); at the grammar level it is still only
424
+ > defined as "an arbitrary character sequence wrapped in curly braces."
425
+ > Container Blocks (`@details`, `@card`), Callout Blocks (`@note`, `@tip`,
426
+ > `@important`, `@warning`, `@caution`), and `@img` now formally include it
427
+ > in their respective productions (see §5–7 above), rather than it being
428
+ > incidental Parser-side behavior not explicitly sanctioned by the EBNF.
429
+ >
430
+ > **Token semantics are each node's own rules, not a single shared table.**
431
+ > `@note`/`@tip`/`@important`/`@warning`/`@caution`/`@details` reuse the
432
+ > existing color token rules from Inline Spec §7 `@mark Styles Semantics`
433
+ > (named-token lookup table + hex support); `@card` and `@img` are each
434
+ > their own independent, closed token set — `Card Style v1` (see the
435
+ > "Card Style v1" subsection in §6 below) and `Image Style v1` (see the
436
+ > "Image Style v1" subsection in §5 below) respectively — sharing the same
437
+ > `#RRGGBB` / `radius-N` token shapes, but with independent semantics
438
+ > (`@card`'s hex is a background color, `@img`'s hex is a border color);
439
+ > neither reuses the named color swatch from Inline Spec §7.
440
+ > Whether/how the renderer maps `styles` for Container/Callout/`@img` into
441
+ > visual styling is left to each renderer to decide.
442
+
443
+ ---
444
+
445
+ ## 5. Structural Blocks
446
+
447
+ ### Heading
448
+
449
+ The canonical syntax is `@heading`; `@h` is an equivalent Simplified Alias — both parse to the same AST node, and the renderer does not distinguish which form the author actually typed.
450
+
451
+ ```text
452
+ @heading(1)[
453
+ Introduction
454
+ ]
455
+
456
+ @h(1)[
457
+ Introduction
458
+ ]
459
+ ```
460
+
461
+ HTML (both forms produce the same output):
462
+
463
+ ```html
464
+ <h1>Introduction</h1>
465
+ ```
466
+
467
+ ---
468
+
469
+ ### Paragraph
470
+
471
+ The canonical syntax is `@paragraph`; `@p` is an equivalent Simplified Alias.
472
+
473
+ ```text
474
+ @paragraph[
475
+ Hello World
476
+ ]
477
+
478
+ @p[
479
+ Hello World
480
+ ]
481
+ ```
482
+
483
+ HTML (both forms produce the same output):
484
+
485
+ ```html
486
+ <p>Hello World</p>
487
+ ```
488
+
489
+ ---
490
+
491
+ ### Quote
492
+
493
+ ```text
494
+ @quote[
495
+ Talk is cheap.
496
+ Show me the code.
497
+ ]
498
+ ```
499
+
500
+ HTML:
501
+
502
+ ```html
503
+ <blockquote>
504
+ Talk is cheap.
505
+ Show me the code.
506
+ </blockquote>
507
+ ```
508
+
509
+ ---
510
+
511
+ ### List
512
+
513
+ Any non-empty line is an item; a leading `- ` is an **optional** backward-compatible form that the Parser automatically strips:
514
+
515
+ ```text
516
+ @list[
517
+ Apple
518
+ Banana
519
+ Orange
520
+ ]
521
+ ```
522
+
523
+ This is equivalent to:
524
+
525
+ ```text
526
+ @list[
527
+ - Apple
528
+ - Banana
529
+ - Orange
530
+ ]
531
+ ```
532
+
533
+ Each item is an independent `list-item` node in the AST (`node.items`); its content can include inline nodes (e.g. `@bold`), not just plain text:
534
+
535
+ ```text
536
+ @list[
537
+ @bold[Apple] (today's special)
538
+ Banana
539
+ ]
540
+ ```
541
+
542
+ AST:
543
+
544
+ ```text
545
+ List
546
+ └── items
547
+ ├── ListItem [ Bold("Apple"), " (today's special)" ]
548
+ └── ListItem [ "Banana" ]
549
+ ```
550
+
551
+ > [!TIP]
552
+ > **TIP**: The old semantics required "must start with `- ` to count as an item," which was inconsistent with the intuition that "a newline means a new item," and also led every renderer (Route A / Route B / ...) to reimplement its own list-splitting logic via string processing. Under the new semantics, the Parser uniformly produces the `ListItem` AST, and renderers only need to render the existing structure without splitting strings themselves.
553
+
554
+ #### Ordered List
555
+
556
+ `@list(ordered)[...]` renders as `<ol>` instead of the default `<ul>`. As with a regular `@list`, a leading `- ` is optional and not required — a plain text line still counts as an item; explicitly writing `N. `/`N)` specifies the number explicitly, and the Parser stores that number in the `ListItem`'s `marker` field. The renderer only converts `marker` into `<li value="N">` when `ordered` is true, leaving the native `<ol>` counter in the browser to handle "auto-continuing after a skipped number":
557
+
558
+ ```text
559
+ @list(ordered)[
560
+ - Apple
561
+ - Banana
562
+ 3. Cherry
563
+ - Date
564
+ ]
565
+ ```
566
+
567
+ The rendered result is `1. Apple`, `2. Banana`, `3. Cherry` (explicitly specified), `4. Date` (auto-continued).
568
+
569
+ #### Nested List
570
+
571
+ No new syntax is introduced — the content of `@list` is already `block-content`, so a nested `@list[...]` is already a valid child node. A nested `@list[...]` occupying its own line (with only whitespace before and after) is merged by the Parser into the content of the **preceding** item, rather than starting a new item:
572
+
573
+ ```text
574
+ @list[
575
+ - Fruits
576
+ @list[
577
+ - Apple
578
+ - Banana
579
+ ]
580
+ - Vegetables
581
+ ]
582
+ ```
583
+
584
+ In the AST, the inner `@list` node appears within the `content` array of the `Fruits` `ListItem`; the renderer needs no extra logic — recursively rendering `content` naturally produces the nested `<ul>`/`<ol>`.
585
+ > An older version of the document once mentioned a proposal to declare list type per level using a `(modifier)` array (e.g. `@list(bullet,number)[...]`); the design above — `@list(ordered)` plus nested sublists each declaring their own type — is the simpler approach that was actually adopted, instead of that proposal.
586
+
587
+ ---
588
+
589
+ ### Code
590
+
591
+ ```text
592
+ @code(ts)[
593
+ const x = 1;
594
+ ]
595
+ ```
596
+
597
+ HTML:
598
+
599
+ ```html
600
+ <pre><code class="language-ts">
601
+ const x = 1;
602
+ </code></pre>
603
+ ```
604
+
605
+ ---
606
+
607
+ ### Image
608
+
609
+ The parenthesized content of `@img` is a comma-separated key=value option list (`image-option-list`), which is extensible. If the first option omits `key=`, it defaults to `src`:
610
+
611
+ ```text
612
+ @img(
613
+ https://example.com/logo.png
614
+ )[
615
+ WEDC Logo
616
+ ]
617
+ ```
618
+
619
+ This is equivalent to:
620
+
621
+ ```text
622
+ @img(src=https://example.com/logo.png)[
623
+ WEDC Logo
624
+ ]
625
+ ```
626
+
627
+ Used together with other options:
628
+
629
+ ```text
630
+ @img(
631
+ https://example.com/logo.png,width=200,align=center
632
+ )[
633
+ WEDC Logo
634
+ ]
635
+ ```
636
+
637
+ Currently supported options:
638
+
639
+ | Option | Description | Example value |
640
+ | ------------- | ----------------------------------- | -------------------------- |
641
+ | `src` (optional) | Image source URL | `src=https://...` |
642
+ | `width` | Display width (unit decided by the renderer) | `width=200` |
643
+ | `height` | Display height (unit decided by the renderer) | `height=150` |
644
+ | `align` | Alignment | `align=left/center/right` |
645
+ | `radius` | Corner radius (CSS value passed directly through to the renderer) | `radius=8px` |
646
+ | `border` | Border (CSS value passed directly through to the renderer) | `border=1px solid #ccc` |
647
+
648
+ > [!TIP]
649
+ > **TIP**: This extension mechanism follows the same design philosophy as Inline Spec §7 `@mark Styles Semantics` — the grammar level only defines "a comma-separated option list inside parentheses"; the actual set of keys belongs to the semantic level, so adding new options in the future (e.g. `alt`, `loading`) does not require modifying the EBNF itself. The renderer MUST ignore unrecognized keys, and SHOULD fall back to "applying only `src`" rather than throwing an error.
650
+
651
+ #### Image Style v1
652
+
653
+ `@img(...)` can be followed by an optional `{styles}` (grammar defined in §4), which shares the same **Card Style v1** token shape (`#RRGGBB` / `radius-N`) as `@card`, but with independent semantics forming its own closed token set — it **does not reuse** the named color swatch from Inline Spec §7, and it is **not** part of `image-option-list` (it cannot be written inside the `(...)` parentheses):
654
+
655
+ | Token shape | Semantics | Example |
656
+ |---|---|---|
657
+ | `#RRGGBB` (hexadecimal) | Border color, applied as a 1px solid border | `@img(src=...){#3366ff}[...]` → `border: 1px solid #3366ff` |
658
+ | `radius-N` (N is a non-negative integer) | Corner radius, `N` is a pixel value | `@img(src=...){radius-12}[...]` → `border-radius: 12px` |
659
+
660
+ ```text
661
+ @img(src=https://example.com/photo.jpg){#3366ff,radius-12}[
662
+ WEDC Photo
663
+ ]
664
+ ```
665
+
666
+ When `{styles}` is omitted, the result is a plain, bare `<img>` with no default corner radius or border — unlike `@card`, which usually has the renderer's own static defaults that can be overridden, `{styles}` for `@img` is purely an "optional" switch, not an "override the default" one.
667
+
668
+ > [!NOTE]
669
+ > **NOTE**: The existing `radius`/`border` `(...)` options (see the table above) remain valid, serving as an escape hatch for cases that need arbitrary CSS values (e.g. `border=2px dashed red`); `{styles}` is the cross-platform, closed-token-set shorthand. When both appear together, the renderer SHOULD let `{styles}` override the corresponding `(...)` option (`{radius-N}` overrides `radius=`, `{#RRGGBB}` overrides `border=`), rather than stacking the two or throwing an error.
670
+
671
+ ---
672
+
673
+ ### Table
674
+
675
+ The internal structure of `@table` is fixed as two dedicated child nodes, `@cols` + `@data`, **in a fixed order, both required**:
676
+
677
+ ```text
678
+ @table[
679
+ @cols[id,name,price]
680
+
681
+ @data[
682
+ [1,Breakfast,60]
683
+ [2,Lunch,80]
684
+ [3,Dinner,90]
685
+ ]
686
+ ]
687
+ ```
688
+
689
+ * `@cols[...]`: a comma-separated list of column headers that defines the column order and count; each column is a `cell` (see below) just like `@data`'s cells, not limited to plain-text identifiers.
690
+ * `@data[...]`: each row is wrapped in `[...]`; the number of `cell`s SHOULD match the number of columns defined by `@cols`. The Parser MAY throw a warning or error for a row whose count doesn't match (decided by Strict / Editor Mode, see Inline Spec §11 Parser Recovery Strategy).
691
+
692
+ > [!TIP]
693
+ > **TIP**: The fixed order and required presence of both `@cols` and `@data` is a deliberate design trade-off — sacrificing a bit of flexibility in exchange for a high degree of predictability, both for the Parser and for AI-generated content.
694
+
695
+ Each `cell` is not merely plain text — besides the text itself, it also allows a curated set of inline formatting nodes (`@bold`, `@italic`, `@underline`, `@del`, `@mark`, `@color`, `@sup`, `@sub`, `@link`, `@fn`, and `@n`, which is converted into a line break), because these nodes only change how the text is presented and do not affect the table's own "columns aligned with data rows" structure. This list is maintained on the renderer side (`isCellAllowedNode` in `registry.ts`); the grammar level itself does not restrict the list's contents, and it can be extended in the future. A node not on the list (e.g. block nodes that bring their own layout structure, such as `@card`, `@table`, `@details`) MUST throw a syntax error rather than being silently dropped — this is consistent with the spirit of Strict Mode (Inline Syntax Specification §11): "prefer to throw an error rather than swallow erroneous content."
696
+
697
+ > [!NOTE]
698
+ > **Exception**: the raw family of nodes (`@code`, `@mermaid`, `@raw`, `@kbd`) are also not on the `isCellAllowedNode` list, but they are neither "block nodes that bring their own layout structure" nor subject to the MUST-throw rule above — `Parser.ts` (`parseInlineCellList`) deliberately flattens their raw content into plain text inside the cell, rather than throwing an error or parsing them as real nodes. This is separate behavior from how structural nodes like `@card`/`@table`/`@details` are handled; see the comments above `CELL_ALLOWED_INLINE` in `registry.ts` and above `parseInlineCellList` in `Parser.ts` for details.
699
+
700
+ ```text
701
+ @table[
702
+ @cols[id,name,note]
703
+
704
+ @data[
705
+ [1,@bold[Alice],See @link(https://example.com)[profile]@n more info]
706
+ ]
707
+ ]
708
+ ```
709
+
710
+ AST:
711
+
712
+ ```text
713
+ Table
714
+ ├── Columns
715
+ │ ├── id
716
+ │ ├── name
717
+ │ └── price
718
+ └── Rows
719
+ ├── Row [1, Breakfast, 60]
720
+ ├── Row [2, Lunch, 80]
721
+ └── Row [3, Dinner, 90]
722
+ ```
723
+
724
+ Or, with cells containing inline formatting:
725
+
726
+ ```text
727
+ Table
728
+ ├── Columns
729
+ │ ├── id
730
+ │ ├── name
731
+ │ └── note
732
+ └── Rows
733
+ └── Row [
734
+ "1",
735
+ [ Bold("Alice") ],
736
+ [ "See ", Link("https://example.com", "profile"), "\n", " more info" ]
737
+ ]
738
+ ```
739
+
740
+ ---
741
+
742
+ ### Horizontal Rule
743
+
744
+ ```text
745
+ @hr
746
+ ```
747
+
748
+ HTML:
749
+
750
+ ```html
751
+ <hr>
752
+ ```
753
+
754
+ ---
755
+
756
+ ### SVG
757
+
758
+ `@svg` belongs to `raw-block-content`, the same as `@code`/`@mermaid` — the content is preserved verbatim; the Parser does not parse or escape it:
759
+
760
+ ```text
761
+ @svg[
762
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10">
763
+ <circle cx="5" cy="5" r="4" />
764
+ </svg>
765
+ ]
766
+ ```
767
+
768
+ > [!TIP]
769
+ > **TIP**: The content of `@svg` is a **trust boundary** — it is output verbatim by the renderer as a vector graphic actually rendered by the browser, rather than displayed as text the way `@code` is. The renderer SHOULD filter out `<script>` tags and `on*=` event attributes before output (see `sanitizeSvg()` in `Adapters.ts`), but this is a renderer responsibility, not a grammar-level guarantee; `@svg` content from untrusted sources should still be content-reviewed at an earlier stage.
770
+
771
+ ---
772
+
773
+ ## 6. Container Blocks
774
+
775
+ `@details`/`@card` now also accept an **optional** `{styles}` (grammar defined in §4), placed after `(title)` and before `block-content`:
776
+
777
+ ```text
778
+ @card(API Key){#3366ff,radius-12}[
779
+ Put the description content here.
780
+ ]
781
+ ```
782
+
783
+ When omitted, the plain-content form is retained — both are valid. The renderer MAY ignore unrecognized tokens (consistent with the spirit of Inline Spec §6 Unknown Command Fallback). The semantics of `@card`'s `{styles}` tokens are its own closed rule set (`Card Style v1`), unrelated to the named color token lookup table in Inline Spec §7 — see the "Card Style v1" subsection below.
784
+
785
+ ### Details
786
+
787
+ ```text
788
+ @details(Show more)[
789
+ Content
790
+ ]
791
+ ```
792
+
793
+ HTML:
794
+
795
+ ```html
796
+ <details>
797
+ <summary>Show more</summary>
798
+ Content
799
+ </details>
800
+ ```
801
+
802
+ ---
803
+
804
+ ### Card
805
+
806
+ ```text
807
+ @card(API Key)[
808
+ Put the description content here.
809
+ ]
810
+ ```
811
+
812
+ #### Card Style v1
813
+
814
+ `@card`'s `{styles}` only allows a small number of cross-platform, high-value styles, deliberately not designed as a general-purpose CSS escape hatch — currently only the following two token shapes are recognized; they can each appear alone, be combined together (comma-separated, order not significant), or be omitted entirely:
815
+
816
+ | Token shape | Semantics | Example |
817
+ |---|---|---|
818
+ | `#RRGGBB` (hexadecimal) | Background color, using the value directly | `@card{#3366ff}[...]` → `background-color: #3366ff` |
819
+ | `radius-N` (N is a non-negative integer) | Corner radius, `N` is a pixel value | `@card{radius-12}[...]` → `border-radius: 12px` |
820
+
821
+ ```text
822
+ @card{#3366ff,radius-12}[
823
+ Setting both background color and corner radius at the same time.
824
+ ]
825
+ ```
826
+
827
+ Tokens not in the table above (e.g. named color words, or the color tokens from Inline Spec §7) are always treated as unrecognized; the renderer MUST ignore them rather than throwing an error (consistent with the spirit of Inline Spec §6 Unknown Command Fallback), and fall back to its own default appearance. If `radius-N`'s `N` is not a pure number (e.g. `radius-lg`), it is likewise treated as unrecognized.
828
+
829
+ ---
830
+
831
+ ## 7. Callout Blocks
832
+
833
+ `@note`, `@tip`, `@important`, `@warning`, and `@caution` can all be paired with an **optional** `(title)` (defined in §4) to attach a title field independent of the body content; they can also be paired with an **optional** `{styles}` (see §4 and the same explanation in §6 Container Blocks), placed after `(title)`. When both are omitted, the plain-content form is retained — all forms are valid:
834
+
835
+ ### Note
836
+
837
+ ```text
838
+ @note[
839
+ This is general information.
840
+ ]
841
+ ```
842
+
843
+ ---
844
+
845
+ ### Tip
846
+
847
+ ```text
848
+ @tip[
849
+ This is a best-practice recommendation.
850
+ ]
851
+ ```
852
+
853
+ ---
854
+
855
+ ### Important
856
+
857
+ ```text
858
+ @important[
859
+ Please read this content first.
860
+ ]
861
+ ```
862
+
863
+ ---
864
+
865
+ ### Warning
866
+
867
+ ```text
868
+ @warning[
869
+ Cannot be undone after deletion.
870
+ ]
871
+ ```
872
+
873
+ With a title:
874
+
875
+ ```text
876
+ @warning(Data Retention Policy)[
877
+ Cannot be undone after deletion.
878
+ ]
879
+ ```
880
+
881
+ ---
882
+
883
+ ### Caution
884
+
885
+ ```text
886
+ @caution[
887
+ This operation may cause data loss.
888
+ ]
889
+ ```
890
+
891
+ ---
892
+
893
+ ## 8. Widget Blocks
894
+
895
+ ### Tabs
896
+
897
+ `@tabs` can **only** contain one or more `@tab` child nodes internally; it does not accept other block-nodes or bare text:
898
+
899
+ ```text
900
+ @tabs[
901
+ @tab(JavaScript)[
902
+ ...
903
+ ]
904
+
905
+ @tab(Python)[
906
+ ...
907
+ ]
908
+
909
+ @tab(Rust)[
910
+ ...
911
+ ]
912
+ ]
913
+ ```
914
+
915
+ * `@tab(title)[content]`: `title` is the tab's display name, and `content` is a full `block-content` (may contain any block-node and inline-stream).
916
+ * If a node other than `@tab` appears inside `@tabs[...]` (e.g. bare text or another block-node), the Parser MUST treat it as a syntax error (Strict Mode), or Editor Mode automatically ignores it / prompts a fix.
917
+
918
+ > [!TIP]
919
+ > **TIP**: The reason `@tab` is not folded into `block-node` is to prevent it from being misused outside of `@tabs` (e.g. placed directly at the top level of a document). This is similar in spirit to the Opaque Domain design of `@raw` in the Inline Spec: specific syntax is only valid in a specific context.
920
+
921
+ ---
922
+
923
+ ### Mermaid
924
+
925
+ ```text
926
+ @mermaid[
927
+ graph TD
928
+ A --> B
929
+ ]
930
+ ```
931
+
932
+ ---
933
+
934
+ ## 9. Metadata
935
+
936
+ ```text
937
+ @meta[
938
+ title = @Doc
939
+ author = WEDC
940
+ description = AI Native Document Format
941
+ keywords = parser,ast,dsl
942
+ ]
943
+ ```
944
+
945
+ The renderer can map this to:
946
+
947
+ * HTML Meta Tags
948
+ * OpenGraph
949
+ * PDF Metadata
950
+ * DOCX Properties
951
+ * Search Index
952
+ * RAG Metadata
953
+
954
+ ---
955
+
956
+ ## 10. Core Principle
957
+
958
+ The goal of @Doc Block Syntax is not to invent a new HTML.
959
+
960
+ but rather to establish:
961
+
962
+ > Human Editable
963
+ > Machine Deterministic
964
+ > AI Friendly
965
+ > Cross Platform
966
+
967
+ for the document AST.
968
+
969
+ HTML is a renderer.
970
+
971
+ Markdown is a renderer.
972
+
973
+ React is a renderer.
974
+
975
+ And @Doc is:
976
+
977
+ > Source of Truth.
978
+
979
+ ---
980
+
981
+ ## 11. Simplified Syntax Aliases
982
+
983
+ Some high-frequency commands additionally provide a Simplified Alias — purely a shorthand at input time. The Parser normalizes it to the canonical name before creating the AST node (`node.type` is always the canonical name); the renderer never needs to, and never does, distinguish which form the author actually typed.
984
+
985
+ Aliases covered by Block Syntax:
986
+
987
+ | Canonical | Alias |
988
+ |---|---|
989
+ | `@heading` | `@h` |
990
+ | `@paragraph` | `@p` |
991
+
992
+ (The Inline Syntax aliases `@b`/`@i`/`@u` for `@bold`/`@italic`/`@underline` are defined in the Inline Syntax Specification.)