eleven / messenger Public

Clone
git clone https://amadan.net/eleven/messenger

Plain git — no account needed to clone.

Download

Download this file

1{
2 "description": "Markdown rendering contract (issue #531): message text -> platform-neutral semantic tree. Both parsers — web (vendored markdown-it walked by web/static/markdown.js) and native (apple/swift-markdown walked by MarkdownTree.swift) — must normalize every input to exactly this tree. Blocks: p{children} quote{children} ul{items} ol{start,items} codeblock{text,lang}. Inlines: text{text} br code{text} link{href,children} strong/em/strike{children}. Adjacent text runs always merge. These vectors are the spec: when the twins disagree, fix the implementations, never the vectors.",
3 "vectors": [
4 {
5 "name": "plain_text",
6 "input": "hello world",
7 "plain": "hello world",
8 "tree": [
9 {
10 "t": "p",
11 "children": [
12 {
13 "t": "text",
14 "text": "hello world"
15 }
16 ]
17 }
18 ]
19 },
20 {
21 "name": "empty_message",
22 "input": "",
23 "tree": [],
24 "note": "no blocks at all — renderers must tolerate an empty tree"
25 },
26 {
27 "name": "whitespace_only",
28 "input": " ",
29 "tree": []
30 },
31 {
32 "name": "bold",
33 "input": "**b**",
34 "plain": "b",
35 "tree": [
36 {
37 "t": "p",
38 "children": [
39 {
40 "t": "strong",
41 "children": [
42 {
43 "t": "text",
44 "text": "b"
45 }
46 ]
47 }
48 ]
49 }
50 ]
51 },
52 {
53 "name": "italic_star",
54 "input": "*i*",
55 "tree": [
56 {
57 "t": "p",
58 "children": [
59 {
60 "t": "em",
61 "children": [
62 {
63 "t": "text",
64 "text": "i"
65 }
66 ]
67 }
68 ]
69 }
70 ]
71 },
72 {
73 "name": "italic_underscore",
74 "input": "_i_",
75 "tree": [
76 {
77 "t": "p",
78 "children": [
79 {
80 "t": "em",
81 "children": [
82 {
83 "t": "text",
84 "text": "i"
85 }
86 ]
87 }
88 ]
89 }
90 ]
91 },
92 {
93 "name": "bold_italic",
94 "input": "***bi***",
95 "tree": [
96 {
97 "t": "p",
98 "children": [
99 {
100 "t": "em",
101 "children": [
102 {
103 "t": "strong",
104 "children": [
105 {
106 "t": "text",
107 "text": "bi"
108 }
109 ]
110 }
111 ]
112 }
113 ]
114 }
115 ],
116 "note": "CommonMark nests em > strong (the old grammar rendered strong > em — visually identical)"
117 },
118 {
119 "name": "intraword_underscore_literal",
120 "input": "snake_case_name",
121 "tree": [
122 {
123 "t": "p",
124 "children": [
125 {
126 "t": "text",
127 "text": "snake_case_name"
128 }
129 ]
130 }
131 ],
132 "note": "CommonMark forbids intraword _ emphasis, matching the old grammar's word-boundary gate"
133 },
134 {
135 "name": "intraword_star_formats",
136 "input": "in*ner*",
137 "tree": [
138 {
139 "t": "p",
140 "children": [
141 {
142 "t": "text",
143 "text": "in"
144 },
145 {
146 "t": "em",
147 "children": [
148 {
149 "t": "text",
150 "text": "ner"
151 }
152 ]
153 }
154 ]
155 }
156 ]
157 },
158 {
159 "name": "spaced_stars_literal",
160 "input": "a * b * c",
161 "tree": [
162 {
163 "t": "p",
164 "children": [
165 {
166 "t": "text",
167 "text": "a * b * c"
168 }
169 ]
170 }
171 ],
172 "note": "flanking rules: the old grammar wrongly italicised ' b ' here — deliberate fix"
173 },
174 {
175 "name": "nested_marks",
176 "input": "**b _i_**",
177 "tree": [
178 {
179 "t": "p",
180 "children": [
181 {
182 "t": "strong",
183 "children": [
184 {
185 "t": "text",
186 "text": "b "
187 },
188 {
189 "t": "em",
190 "children": [
191 {
192 "t": "text",
193 "text": "i"
194 }
195 ]
196 }
197 ]
198 }
199 ]
200 }
201 ]
202 },
203 {
204 "name": "inline_code",
205 "input": "`co*de*`",
206 "plain": "co*de*",
207 "tree": [
208 {
209 "t": "p",
210 "children": [
211 {
212 "t": "code",
213 "text": "co*de*"
214 }
215 ]
216 }
217 ],
218 "note": "markers never format inside code"
219 },
220 {
221 "name": "inline_code_backtick",
222 "input": "``a`b``",
223 "tree": [
224 {
225 "t": "p",
226 "children": [
227 {
228 "t": "code",
229 "text": "a`b"
230 }
231 ]
232 }
233 ]
234 },
235 {
236 "name": "multiple_code_spans_with_link_like_content",
237 "input": "hello `example.com` and `other.com` OK?",
238 "plain": "hello example.com and other.com OK?",
239 "tree": [
240 {
241 "t": "p",
242 "children": [
243 {
244 "t": "text",
245 "text": "hello "
246 },
247 {
248 "t": "code",
249 "text": "example.com"
250 },
251 {
252 "t": "text",
253 "text": " and "
254 },
255 {
256 "t": "code",
257 "text": "other.com"
258 },
259 {
260 "t": "text",
261 "text": " OK?"
262 }
263 ]
264 }
265 ],
266 "note": "issue #818: two SEPARATE code spans, each ending at its own nearest closing backtick — not one span greedily spanning first-backtick to last-backtick. Neither domain-looking span autolinks; code content never re-parses."
267 },
268 {
269 "name": "strike_single_tilde",
270 "input": "~s~",
271 "tree": [
272 {
273 "t": "p",
274 "children": [
275 {
276 "t": "strike",
277 "children": [
278 {
279 "t": "text",
280 "text": "s"
281 }
282 ]
283 }
284 ]
285 }
286 ],
287 "note": "history: old messages use the original grammar's single ~"
288 },
289 {
290 "name": "strike_double_tilde",
291 "input": "~~s~~",
292 "tree": [
293 {
294 "t": "p",
295 "children": [
296 {
297 "t": "strike",
298 "children": [
299 {
300 "t": "text",
301 "text": "s"
302 }
303 ]
304 }
305 ]
306 }
307 ]
308 },
309 {
310 "name": "strike_mismatched_literal",
311 "input": "~~s~",
312 "tree": [
313 {
314 "t": "p",
315 "children": [
316 {
317 "t": "text",
318 "text": "~~s~"
319 }
320 ]
321 }
322 ],
323 "note": "an opener only pairs with a closer of the same length"
324 },
325 {
326 "name": "strike_triple_tilde_inline_literal",
327 "input": "x ~~~s~~~ y",
328 "tree": [
329 {
330 "t": "p",
331 "children": [
332 {
333 "t": "text",
334 "text": "x ~~~s~~~ y"
335 }
336 ]
337 }
338 ],
339 "note": "runs of 3+ tildes never strike (cmark-gfm)"
340 },
341 {
342 "name": "tilde_fence_info_string",
343 "input": "~~~s~~~",
344 "tree": [
345 {
346 "t": "codeblock",
347 "text": "",
348 "lang": "s~~~"
349 }
350 ],
351 "note": "at line start this is a spec-valid unterminated tilde fence with info string 's~~~' — both parsers agree"
352 },
353 {
354 "name": "strike_inside_em",
355 "input": "*a ~b~ c*",
356 "tree": [
357 {
358 "t": "p",
359 "children": [
360 {
361 "t": "em",
362 "children": [
363 {
364 "t": "text",
365 "text": "a "
366 },
367 {
368 "t": "strike",
369 "children": [
370 {
371 "t": "text",
372 "text": "b"
373 }
374 ]
375 },
376 {
377 "t": "text",
378 "text": " c"
379 }
380 ]
381 }
382 ]
383 }
384 ]
385 },
386 {
387 "name": "bold_link_interplay",
388 "input": "**github.com/apps/x**",
389 "tree": [
390 {
391 "t": "p",
392 "children": [
393 {
394 "t": "strong",
395 "children": [
396 {
397 "t": "link",
398 "href": "https://github.com/apps/x",
399 "children": [
400 {
401 "t": "text",
402 "text": "github.com/apps/x"
403 }
404 ]
405 }
406 ]
407 }
408 ]
409 }
410 ],
411 "note": "the motivating #531 bug: emphasis spanning a bare link"
412 },
413 {
414 "name": "bare_https_url",
415 "input": "see https://example.com/a?b=1 now",
416 "tree": [
417 {
418 "t": "p",
419 "children": [
420 {
421 "t": "text",
422 "text": "see "
423 },
424 {
425 "t": "link",
426 "href": "https://example.com/a?b=1",
427 "children": [
428 {
429 "t": "text",
430 "text": "https://example.com/a?b=1"
431 }
432 ]
433 },
434 {
435 "t": "text",
436 "text": " now"
437 }
438 ]
439 }
440 ]
441 },
442 {
443 "name": "www_url",
444 "input": "www.example.com",
445 "tree": [
446 {
447 "t": "p",
448 "children": [
449 {
450 "t": "link",
451 "href": "https://www.example.com",
452 "children": [
453 {
454 "t": "text",
455 "text": "www.example.com"
456 }
457 ]
458 }
459 ]
460 }
461 ]
462 },
463 {
464 "name": "bare_tld_allowlisted",
465 "input": "try example.com",
466 "tree": [
467 {
468 "t": "p",
469 "children": [
470 {
471 "t": "text",
472 "text": "try "
473 },
474 {
475 "t": "link",
476 "href": "https://example.com",
477 "children": [
478 {
479 "t": "text",
480 "text": "example.com"
481 }
482 ]
483 }
484 ]
485 }
486 ]
487 },
488 {
489 "name": "filenames_stay_plain",
490 "input": "main.js and node.go",
491 "tree": [
492 {
493 "t": "p",
494 "children": [
495 {
496 "t": "text",
497 "text": "main.js and node.go"
498 }
499 ]
500 }
501 ],
502 "note": "BARE_TLDS excludes file extensions"
503 },
504 {
505 "name": "email_not_linked",
506 "input": "mail a@b.com",
507 "tree": [
508 {
509 "t": "p",
510 "children": [
511 {
512 "t": "text",
513 "text": "mail a@b.com"
514 }
515 ]
516 }
517 ]
518 },
519 {
520 "name": "explicit_link",
521 "input": "[docs](https://example.com/a)",
522 "plain": "docs",
523 "tree": [
524 {
525 "t": "p",
526 "children": [
527 {
528 "t": "link",
529 "href": "https://example.com/a",
530 "children": [
531 {
532 "t": "text",
533 "text": "docs"
534 }
535 ]
536 }
537 ]
538 }
539 ]
540 },
541 {
542 "name": "explicit_link_formatted_label",
543 "input": "[**b**](https://example.com)",
544 "tree": [
545 {
546 "t": "p",
547 "children": [
548 {
549 "t": "link",
550 "href": "https://example.com",
551 "children": [
552 {
553 "t": "strong",
554 "children": [
555 {
556 "t": "text",
557 "text": "b"
558 }
559 ]
560 }
561 ]
562 }
563 ]
564 }
565 ]
566 },
567 {
568 "name": "no_autolink_inside_link_label",
569 "input": "[see example.com](https://y.com)",
570 "tree": [
571 {
572 "t": "p",
573 "children": [
574 {
575 "t": "link",
576 "href": "https://y.com",
577 "children": [
578 {
579 "t": "text",
580 "text": "see example.com"
581 }
582 ]
583 }
584 ]
585 }
586 ],
587 "note": "links never nest — bare URLs in a label stay text"
588 },
589 {
590 "name": "angle_autolink",
591 "input": "<https://example.com>",
592 "tree": [
593 {
594 "t": "p",
595 "children": [
596 {
597 "t": "link",
598 "href": "https://example.com",
599 "children": [
600 {
601 "t": "text",
602 "text": "https://example.com"
603 }
604 ]
605 }
606 ]
607 }
608 ]
609 },
610 {
611 "name": "javascript_link_literal",
612 "input": "[x](javascript:alert(1))",
613 "tree": [
614 {
615 "t": "p",
616 "children": [
617 {
618 "t": "text",
619 "text": "[x](javascript:alert(1))"
620 }
621 ]
622 }
623 ],
624 "note": "validateLink: http(s) only — rejected links never tokenize"
625 },
626 {
627 "name": "relative_link_literal",
628 "input": "[x](/rel)",
629 "tree": [
630 {
631 "t": "p",
632 "children": [
633 {
634 "t": "text",
635 "text": "[x](/rel)"
636 }
637 ]
638 }
639 ]
640 },
641 {
642 "name": "mailto_link_literal",
643 "input": "[x](mailto:a@b.c)",
644 "tree": [
645 {
646 "t": "p",
647 "children": [
648 {
649 "t": "text",
650 "text": "[x](mailto:a@b.c)"
651 }
652 ]
653 }
654 ]
655 },
656 {
657 "name": "heading_stays_literal",
658 "input": "# hi **b**",
659 "tree": [
660 {
661 "t": "p",
662 "children": [
663 {
664 "t": "text",
665 "text": "# hi "
666 },
667 {
668 "t": "strong",
669 "children": [
670 {
671 "t": "text",
672 "text": "b"
673 }
674 ]
675 }
676 ]
677 }
678 ],
679 "note": "heading rule never enabled; inline still formats"
680 },
681 {
682 "name": "hr_stays_literal",
683 "input": "---",
684 "tree": [
685 {
686 "t": "p",
687 "children": [
688 {
689 "t": "text",
690 "text": "---"
691 }
692 ]
693 }
694 ]
695 },
696 {
697 "name": "setext_stays_literal",
698 "input": "title\n===",
699 "tree": [
700 {
701 "t": "p",
702 "children": [
703 {
704 "t": "text",
705 "text": "title"
706 },
707 {
708 "t": "br"
709 },
710 {
711 "t": "text",
712 "text": "==="
713 }
714 ]
715 }
716 ]
717 },
718 {
719 "name": "table_stays_literal",
720 "input": "| a | b |\n| - | - |",
721 "tree": [
722 {
723 "t": "p",
724 "children": [
725 {
726 "t": "text",
727 "text": "| a | b |"
728 },
729 {
730 "t": "br"
731 },
732 {
733 "t": "text",
734 "text": "| - | - |"
735 }
736 ]
737 }
738 ]
739 },
740 {
741 "name": "image_stays_literal",
742 "input": "![alt](https://x.com/i.png)",
743 "tree": [
744 {
745 "t": "p",
746 "children": [
747 {
748 "t": "text",
749 "text": "![alt](https://x.com/i.png)"
750 }
751 ]
752 }
753 ],
754 "note": "images reconstruct as literal source text on both twins"
755 },
756 {
757 "name": "image_bad_scheme_literal",
758 "input": "![a](/rel)",
759 "tree": [
760 {
761 "t": "p",
762 "children": [
763 {
764 "t": "text",
765 "text": "![a](/rel)"
766 }
767 ]
768 }
769 ]
770 },
771 {
772 "name": "tasklist_stays_literal",
773 "input": "- [x] task",
774 "tree": [
775 {
776 "t": "ul",
777 "items": [
778 [
779 {
780 "t": "p",
781 "children": [
782 {
783 "t": "text",
784 "text": "[x] task"
785 }
786 ]
787 }
788 ]
789 ]
790 }
791 ],
792 "note": "native re-synthesizes the checkbox prefix cmark-gfm force-parses"
793 },
794 {
795 "name": "inline_html_literal",
796 "input": "<b>hi</b> **b**",
797 "tree": [
798 {
799 "t": "p",
800 "children": [
801 {
802 "t": "text",
803 "text": "<b>hi</b> "
804 },
805 {
806 "t": "strong",
807 "children": [
808 {
809 "t": "text",
810 "text": "b"
811 }
812 ]
813 }
814 ]
815 }
816 ],
817 "note": "native merges adjacent text around InlineHTML to match"
818 },
819 {
820 "name": "indented_code_stays_plain",
821 "input": " indented",
822 "tree": [
823 {
824 "t": "p",
825 "children": [
826 {
827 "t": "text",
828 "text": "indented"
829 }
830 ]
831 }
832 ],
833 "note": "the 4-space rule is never enabled — it mangles pasted text"
834 },
835 {
836 "name": "smart_typography_stays_literal",
837 "input": "\"quotes\" -- dashes...",
838 "tree": [
839 {
840 "t": "p",
841 "children": [
842 {
843 "t": "text",
844 "text": "\"quotes\" -- dashes..."
845 }
846 ]
847 }
848 ],
849 "note": "pins native's .disableSmartOpts (cmark curls quotes by default)"
850 },
851 {
852 "name": "bullet_list",
853 "input": "- a\n- b",
854 "plain": "a b",
855 "tree": [
856 {
857 "t": "ul",
858 "items": [
859 [
860 {
861 "t": "p",
862 "children": [
863 {
864 "t": "text",
865 "text": "a"
866 }
867 ]
868 }
869 ],
870 [
871 {
872 "t": "p",
873 "children": [
874 {
875 "t": "text",
876 "text": "b"
877 }
878 ]
879 }
880 ]
881 ]
882 }
883 ]
884 },
885 {
886 "name": "nested_list",
887 "input": "- a\n - b",
888 "tree": [
889 {
890 "t": "ul",
891 "items": [
892 [
893 {
894 "t": "p",
895 "children": [
896 {
897 "t": "text",
898 "text": "a"
899 }
900 ]
901 },
902 {
903 "t": "ul",
904 "items": [
905 [
906 {
907 "t": "p",
908 "children": [
909 {
910 "t": "text",
911 "text": "b"
912 }
913 ]
914 }
915 ]
916 ]
917 }
918 ]
919 ]
920 }
921 ]
922 },
923 {
924 "name": "ordered_list_start",
925 "input": "3. a\n4. b",
926 "tree": [
927 {
928 "t": "ol",
929 "start": 3,
930 "items": [
931 [
932 {
933 "t": "p",
934 "children": [
935 {
936 "t": "text",
937 "text": "a"
938 }
939 ]
940 }
941 ],
942 [
943 {
944 "t": "p",
945 "children": [
946 {
947 "t": "text",
948 "text": "b"
949 }
950 ]
951 }
952 ]
953 ]
954 }
955 ]
956 },
957 {
958 "name": "loose_list_normalizes_tight",
959 "input": "- a\n\n- b",
960 "tree": [
961 {
962 "t": "ul",
963 "items": [
964 [
965 {
966 "t": "p",
967 "children": [
968 {
969 "t": "text",
970 "text": "a"
971 }
972 ]
973 }
974 ],
975 [
976 {
977 "t": "p",
978 "children": [
979 {
980 "t": "text",
981 "text": "b"
982 }
983 ]
984 }
985 ]
986 ]
987 }
988 ],
989 "note": "loose/tight is erased — the tree (and rendering) is always tight"
990 },
991 {
992 "name": "empty_list_item",
993 "input": "- \n- b",
994 "tree": [
995 {
996 "t": "ul",
997 "items": [
998 [],
999 [
1000 {
1001 "t": "p",
1002 "children": [
1003 {
1004 "t": "text",
1005 "text": "b"
1006 }
1007 ]
1008 }
1009 ]
1010 ]
1011 }
1012 ]
1013 },
1014 {
1015 "name": "paren_ordered_marker",
1016 "input": "1) paren marker",
1017 "tree": [
1018 {
1019 "t": "ol",
1020 "start": 1,
1021 "items": [
1022 [
1023 {
1024 "t": "p",
1025 "children": [
1026 {
1027 "t": "text",
1028 "text": "paren marker"
1029 }
1030 ]
1031 }
1032 ]
1033 ]
1034 }
1035 ]
1036 },
1037 {
1038 "name": "list_interrupts_paragraph",
1039 "input": "text\n- a\n- b",
1040 "tree": [
1041 {
1042 "t": "p",
1043 "children": [
1044 {
1045 "t": "text",
1046 "text": "text"
1047 }
1048 ]
1049 },
1050 {
1051 "t": "ul",
1052 "items": [
1053 [
1054 {
1055 "t": "p",
1056 "children": [
1057 {
1058 "t": "text",
1059 "text": "a"
1060 }
1061 ]
1062 }
1063 ],
1064 [
1065 {
1066 "t": "p",
1067 "children": [
1068 {
1069 "t": "text",
1070 "text": "b"
1071 }
1072 ]
1073 }
1074 ]
1075 ]
1076 }
1077 ]
1078 },
1079 {
1080 "name": "blockquote",
1081 "input": "> q **b**",
1082 "tree": [
1083 {
1084 "t": "quote",
1085 "children": [
1086 {
1087 "t": "p",
1088 "children": [
1089 {
1090 "t": "text",
1091 "text": "q "
1092 },
1093 {
1094 "t": "strong",
1095 "children": [
1096 {
1097 "t": "text",
1098 "text": "b"
1099 }
1100 ]
1101 }
1102 ]
1103 }
1104 ]
1105 }
1106 ]
1107 },
1108 {
1109 "name": "blockquote_lazy_continuation",
1110 "input": "> a\nb",
1111 "tree": [
1112 {
1113 "t": "quote",
1114 "children": [
1115 {
1116 "t": "p",
1117 "children": [
1118 {
1119 "t": "text",
1120 "text": "a"
1121 },
1122 {
1123 "t": "br"
1124 },
1125 {
1126 "t": "text",
1127 "text": "b"
1128 }
1129 ]
1130 }
1131 ]
1132 }
1133 ]
1134 },
1135 {
1136 "name": "blockquote_multi_paragraph",
1137 "input": "> a\n>\n> b",
1138 "tree": [
1139 {
1140 "t": "quote",
1141 "children": [
1142 {
1143 "t": "p",
1144 "children": [
1145 {
1146 "t": "text",
1147 "text": "a"
1148 }
1149 ]
1150 },
1151 {
1152 "t": "p",
1153 "children": [
1154 {
1155 "t": "text",
1156 "text": "b"
1157 }
1158 ]
1159 }
1160 ]
1161 }
1162 ]
1163 },
1164 {
1165 "name": "nested_blockquote",
1166 "input": "> > deep",
1167 "tree": [
1168 {
1169 "t": "quote",
1170 "children": [
1171 {
1172 "t": "quote",
1173 "children": [
1174 {
1175 "t": "p",
1176 "children": [
1177 {
1178 "t": "text",
1179 "text": "deep"
1180 }
1181 ]
1182 }
1183 ]
1184 }
1185 ]
1186 }
1187 ]
1188 },
1189 {
1190 "name": "list_in_blockquote",
1191 "input": "> - a\n> - b",
1192 "tree": [
1193 {
1194 "t": "quote",
1195 "children": [
1196 {
1197 "t": "ul",
1198 "items": [
1199 [
1200 {
1201 "t": "p",
1202 "children": [
1203 {
1204 "t": "text",
1205 "text": "a"
1206 }
1207 ]
1208 }
1209 ],
1210 [
1211 {
1212 "t": "p",
1213 "children": [
1214 {
1215 "t": "text",
1216 "text": "b"
1217 }
1218 ]
1219 }
1220 ]
1221 ]
1222 }
1223 ]
1224 }
1225 ]
1226 },
1227 {
1228 "name": "fence_in_blockquote",
1229 "input": "> ```\n> code\n> ```",
1230 "tree": [
1231 {
1232 "t": "quote",
1233 "children": [
1234 {
1235 "t": "codeblock",
1236 "text": "code",
1237 "lang": ""
1238 }
1239 ]
1240 }
1241 ]
1242 },
1243 {
1244 "name": "fenced_code",
1245 "input": "```js\ncode **here**\n```",
1246 "plain": "code **here**",
1247 "tree": [
1248 {
1249 "t": "codeblock",
1250 "text": "code **here**",
1251 "lang": "js"
1252 }
1253 ],
1254 "note": "trailing newline stripped from the tree's text"
1255 },
1256 {
1257 "name": "unterminated_fence",
1258 "input": "```\nunterminated",
1259 "tree": [
1260 {
1261 "t": "codeblock",
1262 "text": "unterminated",
1263 "lang": ""
1264 }
1265 ]
1266 },
1267 {
1268 "name": "tilde_fence",
1269 "input": "~~~\ntilde fence\n~~~",
1270 "tree": [
1271 {
1272 "t": "codeblock",
1273 "text": "tilde fence",
1274 "lang": ""
1275 }
1276 ]
1277 },
1278 {
1279 "name": "soft_break",
1280 "input": "a\nb",
1281 "tree": [
1282 {
1283 "t": "p",
1284 "children": [
1285 {
1286 "t": "text",
1287 "text": "a"
1288 },
1289 {
1290 "t": "br"
1291 },
1292 {
1293 "t": "text",
1294 "text": "b"
1295 }
1296 ]
1297 }
1298 ],
1299 "note": "every newline is a break in chat — soft and hard breaks both normalize to br"
1300 },
1301 {
1302 "name": "hard_break_two_spaces",
1303 "input": "line \nhard",
1304 "tree": [
1305 {
1306 "t": "p",
1307 "children": [
1308 {
1309 "t": "text",
1310 "text": "line"
1311 },
1312 {
1313 "t": "br"
1314 },
1315 {
1316 "t": "text",
1317 "text": "hard"
1318 }
1319 ]
1320 }
1321 ]
1322 },
1323 {
1324 "name": "hard_break_backslash",
1325 "input": "back\\\nslash",
1326 "tree": [
1327 {
1328 "t": "p",
1329 "children": [
1330 {
1331 "t": "text",
1332 "text": "back"
1333 },
1334 {
1335 "t": "br"
1336 },
1337 {
1338 "t": "text",
1339 "text": "slash"
1340 }
1341 ]
1342 }
1343 ]
1344 },
1345 {
1346 "name": "paragraph_split",
1347 "input": "a\n\nb",
1348 "tree": [
1349 {
1350 "t": "p",
1351 "children": [
1352 {
1353 "t": "text",
1354 "text": "a"
1355 }
1356 ]
1357 },
1358 {
1359 "t": "p",
1360 "children": [
1361 {
1362 "t": "text",
1363 "text": "b"
1364 }
1365 ]
1366 }
1367 ]
1368 },
1369 {
1370 "name": "backslash_escape",
1371 "input": "\\*lit\\*",
1372 "plain": "*lit*",
1373 "tree": [
1374 {
1375 "t": "p",
1376 "children": [
1377 {
1378 "t": "text",
1379 "text": "*lit*"
1380 }
1381 ]
1382 }
1383 ],
1384 "note": "old clients show the backslashes — the composer serializer escapes minimally for this reason"
1385 },
1386 {
1387 "name": "entity_decodes",
1388 "input": "&amp; &lt;",
1389 "tree": [
1390 {
1391 "t": "p",
1392 "children": [
1393 {
1394 "t": "text",
1395 "text": "& <"
1396 }
1397 ]
1398 }
1399 ],
1400 "note": "cmark decodes entities unconditionally; web enables the rule to match"
1401 },
1402 {
1403 "name": "mention_is_render_layer",
1404 "input": "@Ada hello",
1405 "tree": [
1406 {
1407 "t": "p",
1408 "children": [
1409 {
1410 "t": "text",
1411 "text": "@Ada hello"
1412 }
1413 ]
1414 }
1415 ],
1416 "note": "mention chips are scanned at render time against the roster, never in the tree"
1417 },
1418 {
1419 "name": "emoji_is_render_layer",
1420 "input": "🎉",
1421 "tree": [
1422 {
1423 "t": "p",
1424 "children": [
1425 {
1426 "t": "text",
1427 "text": "🎉"
1428 }
1429 ]
1430 }
1431 ],
1432 "note": "jumbo-emoji detection stays a raw-text helper"
1433 }
1434 ]
1435}
1436