@maxhealth.tech/prefab 0.3.10 → 0.3.11
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/CHANGELOG.md +723 -698
- package/README.md +1 -1
- package/dist/a2ui/catalog.d.ts +22 -1
- package/dist/a2ui/catalog.d.ts.map +1 -1
- package/dist/a2ui/catalog.js +88 -32
- package/dist/a2ui/catalog.js.map +1 -1
- package/dist/a2ui/control.d.ts +20 -0
- package/dist/a2ui/control.d.ts.map +1 -0
- package/dist/a2ui/control.js +107 -0
- package/dist/a2ui/control.js.map +1 -0
- package/dist/a2ui/emit.d.ts.map +1 -1
- package/dist/a2ui/emit.js +113 -9
- package/dist/a2ui/emit.js.map +1 -1
- package/dist/a2ui/expr.d.ts +52 -19
- package/dist/a2ui/expr.d.ts.map +1 -1
- package/dist/a2ui/expr.js +153 -33
- package/dist/a2ui/expr.js.map +1 -1
- package/dist/a2ui/index.d.ts +2 -0
- package/dist/a2ui/index.d.ts.map +1 -1
- package/dist/a2ui/index.js +1 -0
- package/dist/a2ui/index.js.map +1 -1
- package/dist/a2ui/pipes.d.ts +49 -0
- package/dist/a2ui/pipes.d.ts.map +1 -0
- package/dist/a2ui/pipes.js +106 -0
- package/dist/a2ui/pipes.js.map +1 -0
- package/dist/a2ui.min.js +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/renderer.auto.min.js +1 -1
- package/dist/renderer.min.js +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,700 +1,725 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file.
|
|
4
|
-
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
15
|
-
|
|
16
|
-
- `
|
|
17
|
-
- `
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
-
|
|
39
|
-
-
|
|
40
|
-
- **`
|
|
41
|
-
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
###
|
|
54
|
-
|
|
55
|
-
- **
|
|
56
|
-
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
###
|
|
62
|
-
|
|
63
|
-
- **`
|
|
64
|
-
-
|
|
65
|
-
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
- **The
|
|
84
|
-
-
|
|
85
|
-
|
|
86
|
-
### Internal
|
|
87
|
-
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
- **
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
- **
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
-
|
|
109
|
-
- `
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
-
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
|
|
123
|
-
- `
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- `
|
|
134
|
-
-
|
|
135
|
-
-
|
|
136
|
-
|
|
137
|
-
###
|
|
138
|
-
|
|
139
|
-
-
|
|
140
|
-
- Added
|
|
141
|
-
-
|
|
142
|
-
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
###
|
|
151
|
-
|
|
152
|
-
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
-
|
|
168
|
-
|
|
169
|
-
###
|
|
170
|
-
|
|
171
|
-
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
-
|
|
197
|
-
-
|
|
198
|
-
|
|
199
|
-
## [0.3.
|
|
200
|
-
|
|
201
|
-
###
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
- **
|
|
208
|
-
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
- **`
|
|
232
|
-
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
###
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
###
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
- **
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
-
|
|
315
|
-
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
- **Fixed**:
|
|
321
|
-
- **
|
|
322
|
-
- **Fixed**: `
|
|
323
|
-
- **Fixed**:
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
- **
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
-
|
|
348
|
-
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
-
|
|
352
|
-
|
|
353
|
-
### Tests
|
|
354
|
-
-
|
|
355
|
-
-
|
|
356
|
-
- **
|
|
357
|
-
|
|
358
|
-
## [0.2.
|
|
359
|
-
|
|
360
|
-
###
|
|
361
|
-
- **
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
-
|
|
367
|
-
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
375
|
-
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
-
|
|
381
|
-
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
- `
|
|
387
|
-
- `
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
###
|
|
398
|
-
- **Fixed**:
|
|
399
|
-
- `
|
|
400
|
-
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
- `
|
|
411
|
-
- `
|
|
412
|
-
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
-
|
|
424
|
-
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
- `
|
|
441
|
-
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
###
|
|
446
|
-
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
- `
|
|
453
|
-
-
|
|
454
|
-
-
|
|
455
|
-
-
|
|
456
|
-
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
-
|
|
463
|
-
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
- `
|
|
477
|
-
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
-
|
|
481
|
-
-
|
|
482
|
-
|
|
483
|
-
## [0.2.
|
|
484
|
-
|
|
485
|
-
###
|
|
486
|
-
-
|
|
487
|
-
-
|
|
488
|
-
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
-
|
|
495
|
-
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
-
|
|
512
|
-
-
|
|
513
|
-
-
|
|
514
|
-
-
|
|
515
|
-
|
|
516
|
-
## [0.2.
|
|
517
|
-
|
|
518
|
-
###
|
|
519
|
-
- `
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
- `
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
-
|
|
534
|
-
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
-
|
|
540
|
-
|
|
541
|
-
## [0.2.
|
|
542
|
-
|
|
543
|
-
###
|
|
544
|
-
- `
|
|
545
|
-
|
|
546
|
-
## [0.2.
|
|
547
|
-
|
|
548
|
-
###
|
|
549
|
-
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
###
|
|
564
|
-
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
-
|
|
580
|
-
|
|
581
|
-
## [0.2.
|
|
582
|
-
|
|
583
|
-
###
|
|
584
|
-
-
|
|
585
|
-
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
- `
|
|
596
|
-
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
- `
|
|
602
|
-
- `
|
|
603
|
-
-
|
|
604
|
-
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
- `
|
|
619
|
-
-
|
|
620
|
-
- `
|
|
621
|
-
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
###
|
|
626
|
-
- `signal()`
|
|
627
|
-
- `
|
|
628
|
-
-
|
|
629
|
-
-
|
|
630
|
-
- `
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
- `
|
|
636
|
-
- `
|
|
637
|
-
- `
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
- `
|
|
644
|
-
- `
|
|
645
|
-
- `
|
|
646
|
-
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
-
|
|
652
|
-
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
-
|
|
660
|
-
- `
|
|
661
|
-
- `
|
|
662
|
-
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
-
|
|
666
|
-
-
|
|
667
|
-
-
|
|
668
|
-
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
-
|
|
672
|
-
|
|
673
|
-
###
|
|
674
|
-
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
-
|
|
685
|
-
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
-
|
|
689
|
-
-
|
|
690
|
-
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
- `
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
-
|
|
699
|
-
-
|
|
700
|
-
|
|
7
|
+
<!-- Add new entries directly below. Keep this line: it makes a release merge conflict rather than file them under a published version. -->
|
|
8
|
+
|
|
9
|
+
## [0.3.11] — 2026-08-25
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- **Formatting pipes map onto A2UI catalog functions.** A2UI has no expression language, which is why pipes were reported as unbindable, but its catalog has the functions the common ones correspond to: `currency` to `formatCurrency`, `number` and `round` to `formatNumber`, `date`/`time`/`datetime` to `formatDate`, and `pluralize` to `pluralize`. Seven of prefab's twenty-two built-ins now cross over; the rest transform data rather than format it and stay reported, as does a chained pipe, since no single catalog function is two pipes. The date pipes are the one inexact mapping: prefab renders in the reader's locale and `formatDate` requires an explicit TR35 pattern, so one is chosen and a diagnostic says which.
|
|
14
|
+
- **Form validation survives the crossing.** Every A2UI input is `Checkable`, which is the same job prefab's `required` and `inputType` do, and neither was emitted — so a form crossed over with a required field that was no longer required. `TextField`, `CheckBox` and `ChoicePicker` now carry `checks`. No `message` is emitted with them: the rule already says which check failed, and the renderer is better placed to word and localize that. `numeric` is deliberately omitted for number fields, because the catalog requires it to carry a `min` or a `max` and prefab's number inputs carry no range; emitting a bare one fails validation, which is how the omission was found.
|
|
15
|
+
|
|
16
|
+
- **`ForEach` maps onto A2UI's child template.** A2UI has iteration, the same construct `DataTable` already used: one template component instantiated per item, with paths inside it resolving relative to the current row. The emitter simply never wired it, so every loop was dropped. `$item` now resolves to a relative path and `$index` to the `@index` system function.
|
|
17
|
+
- **Mixed literal and template text is interpolated through `formatString`.** A2UI's `formatString` takes a single template string with JSON Pointers embedded as `${/path}`, so `Score: {{ score }}` crosses over intact. The previous code claimed the function "would need an argument list this function has no way to name", which was simply wrong about its signature. A string is interpolated only if every value in it binds; one unbindable expression makes the whole string unbindable, since interpolating half of it would change what the text says.
|
|
18
|
+
- **`Define` / `Use` / `Slot` are resolved at emit time by inlining the definition.** A2UI needs no named-template concept when the agent can expand the tree before sending it. A `Use`'s `overrides` are seeded into the data model and brought into scope by name, so a `{{ title }}` inside the body reads that call's value rather than the model root. Definitions are collected in a pre-pass, since prefab imposes no ordering between a `Use` and its `Define`, and a definition that uses itself reports a cycle rather than recursing until the stack gives out.
|
|
19
|
+
- `CardTitle`, `CardDescription` and `Tooltip` have first-class text mappings. They rendered correctly through the generic fallback but reported a degradation for what is an exact mapping, which accounted for 36 of the 160 diagnostics across the shipped examples.
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
|
|
23
|
+
- **The release merge could file unreleased entries under the version that just shipped.** Promoting `[Unreleased]` on `main` and adding new entries on `dev` are insertions at the same point, so git's line-based merge orders them heading-first and attributes unshipped work to a published version. It does so without conflicting, which is why the release workflow's best-effort sync never had anything to report, and it has now happened three times. `scripts/changelog-release.ts` keeps an anchor line under `[Unreleased]` so the two insertions land at the same offset and collide, and `scripts/check-changelog.ts` verifies the outcome independently in CI: every version section must stay byte-identical to `main`'s. The anchor is a guard, the check is the guarantee.
|
|
24
|
+
- **A dropped node left its already-emitted children orphaned, producing a payload the renderer rejects.** A mapper reads its children before deciding whether it can map at all: `Dialog` emits its body, then finds it cannot build a trigger, returns nothing, and the body stays in the adjacency list with no component pointing at it. `interactive-showcase.json` — one of the shipped examples — emitted an unreachable `Row` on v0.3.10 for exactly this reason. Emission is now transactional: a node that drops rolls back every component and data-model key added while mapping it. The per-type registry tests could not catch this, because each mapper is correct in isolation and the fault is in the interaction; `test/a2ui-control.test.ts` now runs every shipped example through the emitter, which does catch it.
|
|
25
|
+
- **`Define` emitted its template body as visible content.** It fell to the generic fallback and was flattened to a `Column`, so a definition rendered where it was declared. The renderer draws nothing at the point of definition and now neither does the emitter.
|
|
26
|
+
- **The emitter knew only one of the two spellings for a tool-call action.** The renderer accepts `toolCall` and `callTool`, and the shipped examples use the latter, so every hand-written tool call was silently downgraded to a generic agent event and had its arguments dropped for not being scalar.
|
|
27
|
+
- **A string containing two templates was mis-read as one.** The sole-template pattern is lazy but still spanned `}} … {{`, so `{{ a }} of {{ b }}` matched as a single template whose body was `a }} of {{ b`, failed to parse as a path, and was reported unbindable instead of being interpolated.
|
|
28
|
+
- **A `Modal` whose trigger could not map lost the whole dialog.** prefab's dialog trigger is usually a `Button` with no `onClick`, because opening the dialog is implicit, and the `Button` mapper rightly refuses one with no action. A2UI's `Modal` accepts any component as its trigger, so it now falls back to the trigger's label as `Text`.
|
|
29
|
+
|
|
30
|
+
Across the twenty shipped examples this takes the emitter from 160 diagnostics and one invalid payload to 95 diagnostics and none. What remains is largely honest: charts, `Metric` trend styling, table controls, and the conditionals.
|
|
31
|
+
|
|
32
|
+
## [0.3.10] — 2026-08-24
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
|
|
36
|
+
- **A2UI is now a first-class output target: `PrefabApp.toA2UI()` emits the same component tree as [A2UI](https://a2ui.org) v1.0.** A2UI is the Google-backed declarative agent-to-UI protocol, and it overlaps prefab's category exactly: the agent sends a component tree plus a data model, and the host's own renderer draws native widgets from it. It reaches renderers prefab has none of (React, Angular, Lit, Flutter, Swift, Jetpack Compose) with no iframe involved, and its agent-side SDK is Python-only, so a TypeScript authoring layer had no equivalent. Two structural gaps had to be bridged: A2UI keeps components in a flat adjacency list rather than a nested tree, and it binds dynamic values through JSON Pointers rather than interpolating `{{ }}` templates. `src/a2ui/` handles both, allocating ids deterministically so the same tree always emits byte-identical output.
|
|
37
|
+
- **`display_a2ui()` and `registerA2uiResource()` serve A2UI over MCP**, under the `application/a2ui+json` MIME type as an embedded resource in a tool result or as a standalone `a2ui://` resource, per the [A2UI over MCP](https://a2ui.org/guides/a2ui_over_mcp/) guide. They sit alongside the existing `ui://` MCP Apps helpers rather than replacing them, so one server can offer both and let the host choose. A `ui://` viewer is a pure function of the package version and stays shared-cacheable; an `a2ui://` surface is rebuilt on every read and defaults to no caching, so `resolveCache` is now exported from `resource.ts` and takes the defaults as a parameter instead of hard-coding the viewer's.
|
|
38
|
+
- **Multi Round-Trip input requests (protocol revision 2026-07-28).** The revision made the protocol core stateless and removed server-initiated `elicitation/create`; a handler now returns an `input_required` result and the client retries the call with the answers. That left `display_form()` reachable only on hosts that render UI. The same `AutoFormField[]` now also derives the restricted elicitation schema, so `display_form(fields, tool, { elicit: true })` asks for exactly what the rendered form asks for. `formSchema`, `formInputRequest`, `acceptedFormInput` and `inputResponse` are exported for handlers that compose the rounds themselves. `acceptedFormInput` validates the untrusted client response against the same field list: unknown keys, wrong types, out-of-bound numbers and unoffered enum values are dropped, and a missing required field fails the answer.
|
|
39
|
+
- **The playground has an A2UI tab.** The preview pane now switches between the rendered UI and the same payload translated to A2UI, with the message and component counts, a pill per diagnostic kind, and every diagnostic listed underneath. A `Stream` toggle splits the output into the three-message form. Being able to see what a payload costs in translation, on the twenty shipped examples, says more than the mapping table does.
|
|
40
|
+
- **`dist/a2ui.min.js`, the emitter as a standalone browser bundle.** The playground needs the emitter and does not need it inside `renderer.min.js`: almost no page that renders `$prefab` also emits A2UI, so folding it in would tax every consumer for a feature they do not use. It exposes `PrefabA2UI.emit()` and `PrefabA2UI.envelope()`, takes wire JSON rather than a component tree, and comes to 17 KB against the renderer's 108 KB. Attached to GitHub releases alongside the renderer bundles.
|
|
41
|
+
- `AutoFormField` gained `options`, `multiple`, `description`, `min`, `max` and `default`. A field with `options` renders as a `Select` instead of a bare `Input` on the UI path, and as an enum on the elicitation path.
|
|
42
|
+
- `McpDisplayResult<S>` pins `structuredContent` as present. Every display helper populates it, and the optional field on `McpToolResult` was forcing callers into a null check on something that is never absent.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- **README, docs landing page and package description now lead with "TypeScript authoring for A2UI and MCP Apps".** The previous framing put "a superset of PrefectHQ's Python prefab-ui" in the second line, which tied the package's story to a project moving slowly and described an implementation detail rather than what the package is for. The superset relationship is still stated, one bullet down, where it belongs.
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- **The A2UI emitter produced an invalid surface for most icons.** A2UI's `Icon.name` is a closed enum of 59 Material names, so an unrecognised value does not degrade to a fallback glyph — it fails schema validation and takes the component with it. prefab's `Icon(name)` accepts any string and follows Lucide, so every icon outside that enum emitted an invalid payload. The hand-written test view happened to use `Icon('mail')`, which is in the enum, so nothing caught it. `src/a2ui/icons.ts` now normalises the name, matches it against the enum case- and separator-insensitively, translates the Lucide names prefab itself ships (`AlertCircle` → `error`, `CheckCircle` → `check`), and drops the icon with a diagnostic when nothing means the same thing. The enum is duplicated as a literal because the package has no runtime dependencies and cannot read the catalog at emit time; a test asserts it matches the vendored catalog exactly, so a spec revision cannot let it drift.
|
|
51
|
+
- **`test/component-types.test.ts` depended on which test file the runner reached first.** It compared the generated `src/core/component-types.ts` against the whole renderer registry, which is a process-wide singleton any module can add to. `test/renderer-destroy.test.ts` and `test/pipe-wire.test.ts` register a dozen widgets inside their test bodies, so once the runner reached either of them first, all twelve became "renderable but absent from the generated list" and thirteen assertions failed. It passed locally and failed on CI purely on file-discovery order, and adding the A2UI test files was enough to flip it. `registerAllComponents()` now records the delta it contributes as the built-in set, and both the generator and the sync check read that instead of the full registry. The generated list is unchanged at 119 types.
|
|
52
|
+
|
|
53
|
+
### Internal
|
|
54
|
+
|
|
55
|
+
- **Registry-wide A2UI invariants, rather than one hand-written view per mapper family.** The per-family views check that the mappings are right; they cannot check that they are safe, because a mapper is only covered if someone remembered to write a view for it, and the branches that matter most are the guards deciding "this cannot be expressed, drop it and say why". Coverage of `catalog.ts` sat at 72% of functions with nearly every uncovered line being one of those guards. `test/a2ui-registry.test.ts` now asserts two properties across all 81 registry entries, so a mapper added later is covered the moment it is registered: nothing a mapper emits is ever invalid, given generous props or none at all; and nothing is dropped without a diagnostic. The furnished pass additionally requires an actual component, since "emits nothing" would otherwise satisfy the first property trivially. This is what surfaced the `Icon` enum bug. `catalog.ts` is now at 100% of functions and 97% of lines.
|
|
56
|
+
- Removed `hasTemplate` and `isTemplatedList`, both exported with no caller anywhere.
|
|
57
|
+
- Emitted A2UI payloads are validated in CI against the official v1.0 JSON Schemas, vendored under `test/fixtures/a2ui/v1_0/` (Apache-2.0, with a `NOTICE.md` recording the upstream commit) and refreshable with `bun scripts/sync-a2ui-schemas.ts`. They are checked in rather than fetched so the suite runs offline. The upstream YAML conformance suites were considered and rejected: they exercise SDK internals (streaming parser, catalog pruning, validator behaviour) that a producer does not implement, whereas the schemas define exactly what a producer must emit. Two structural rules the schemas cannot express — every child reference resolves, and every component is reachable from `root` — are asserted separately, taken from `conformance/core/validator.yaml`.
|
|
58
|
+
|
|
59
|
+
## [0.3.9] — 2026-08-07
|
|
60
|
+
|
|
61
|
+
### Added
|
|
62
|
+
|
|
63
|
+
- **`label` on every stateful control.** `InputProps` declared it privately and only `renderInput` drew it, so `Select({ label })` and `DatePicker({ label })` were documented but silently dropped. `label` moves to `StatefulProps`, and the wire shape shared by all of them is now one exported `statefulProps()` helper. `Select`, `RadioGroup` and `Combobox` are containers rather than `StatefulComponent`s and were each restating that block by hand, which is exactly how `label` went missing on them; they call the helper now. On the renderer side a single `withLabel()` decorator is applied at registration to the five controls that lay out vertically (`Select`, `DatePicker`, `Combobox`, `Textarea`, `Slider`). Checkbox, Switch, Radio, RadioGroup and ChoiceCard place their own label beside the control and are deliberately not wrapped.
|
|
64
|
+
- **`Tabs({ defaultTab })`** — a `Tab` title or a 0-based index, replacing a hardcoded `i === 0` in the renderer. An unmatched title or out-of-range index falls back to the first tab rather than leaving no tab selected.
|
|
65
|
+
- **`Table({ striped })`** — shades alternate body rows via a `pf-table-striped` modifier and a `:nth-child(even)` rule, rather than per-table styling.
|
|
66
|
+
- **`Sparkline({ width, height })`** — the renderer hardcoded 120×32; those are now the defaults.
|
|
67
|
+
|
|
68
|
+
- **`DropZone` actually handles files.** The renderer was a stub (`// File handling would go here`) with drag listeners and nothing behind them, so the documented `accept` applied to nothing. It now takes files by drag-and-drop or click-to-browse, is keyboard reachable (`role="button"`, Enter/Space), and accepts `label`, `accept`, `multiple`, `resultKey` and `onDrop`. Those names and the `$result` callback binding are the ones the `OpenFilePicker` action already uses, so both routes to a file list behave identically. `accept` is enforced for **dropped** files as well: the browser applies it to the file picker only, so drag-and-drop would otherwise bypass it entirely. Exported `matchesAccept()` handles the three `accept` forms (`image/*`, `.pdf`, `text/csv`); no equivalent existed to reuse, and the org's other drop zones are all React components with no overlap between them.
|
|
69
|
+
|
|
70
|
+
All five were documented in `docs/reference/components.md` with props-table entries before they existed, and were found by `bun run check:docs`.
|
|
71
|
+
|
|
72
|
+
### Docs
|
|
73
|
+
|
|
74
|
+
- **Every fenced `ts` example now typechecks, and CI enforces it.** `bun run check:docs` runs in `ci.yml` between the test and build steps. 71 blocks across 17 files, from 86 errors down to zero. The remaining fixes after the container-shape pass were all stale API forms rather than missing features: `rx` used as a tagged template (it is `rx(key)`, and interpolated text is a plain `'{{ key }}'` string, while `STATE.name` and `ITEM.dot('name')` cover the built-ins), `SelectOption`/`ComboboxOption` documented with an object when they take `(value, label)` positionally, `GridItem`'s `span` which is `colSpan`, `Button`'s `type: 'submit'` which is `submit: true`, `Metric`'s numeric `delta` where the type is `RxStr`, `Dialog`'s `trigger` documented as an element id when it takes a `Component`, `ForEach`'s `as` which does not exist (the loop variable is the exported `ITEM`), and `Slot('name')` which takes props. Four blocks carry `<!-- doccheck: skip -->` with a stated reason: two entry-point listings using `import { ... }`, a CSS side-effect import the bundler resolves rather than tsc, and companion-script code written against the renderer's browser global.
|
|
75
|
+
|
|
76
|
+
## [0.3.8] — 2026-08-05
|
|
77
|
+
|
|
78
|
+
### Fixed
|
|
79
|
+
|
|
80
|
+
- **Strict wire-format validation rejected four renderable component types.** `validateWireFormat(data, { strict: true })` checked `type` against a hand-written list in `src/core/validate.ts` that had fallen behind the renderer's registry: `Condition` (emitted by `If` / `Elif` / `Else`), `Detail`, `MasterDetail` and `PdfViewer` were all registered and renderable, but reported as `Unknown component type`. Any UI using a conditional therefore failed strict validation, as did two of the shipped examples. The list is no longer written by hand: `src/core/component-types.ts` is generated from the registry (`bun run gen:types`, also run by the build), and the validator consumes it. The generated module is import-free, so the validator still works server-side where the renderer is never loaded. Non-strict validation was unaffected, since unknown types are only an error under `strict`.
|
|
81
|
+
- **The shipped example UIs were never validated against the wire format.** `docs/public/examples/*.json` is the single source for the demo, the playground and the generated skill assets, and nothing pointed the package's own `validateWireFormat` at it, so an example could ship a key the renderer ignores and simply render nothing. That had already happened once (0.3.5 repaired examples declaring conditional branches under a non-existent `then` key). `test/doc-examples.test.ts` now validates all 20 in strict mode, which is what surfaced the validator drift above.
|
|
82
|
+
|
|
83
|
+
- **The display helpers could not be returned to an SDK tool handler without a cast.** `PrefabWireFormat` and `PrefabUpdateWire` were interfaces, so the `structuredContent` on every result from `display()`, `display_form()`, `display_update()`, `display_error()` and `display_success()` failed against the SDK's `{ [x: string]: unknown }` with "Index signature for type 'string' is missing". This is the same by-design TypeScript behaviour fixed for `src/mcp/types.ts` in 0.3.7 ([microsoft/TypeScript#15300](https://github.com/microsoft/TypeScript/issues/15300)); these two types were missed because they live outside that file, in `src/app.ts` and `src/mcp/display.ts`. Both are aliases now. Consumers had no way to fix this on their side, since the type came from here, and the only escape was an assertion. prefab carried the same workaround internally: `PrefabApp.toMcpResult()` used `wire as unknown as Record<string, unknown>`, a double assertion that only existed because of this, and is gone.
|
|
84
|
+
- **The 0.3.7 interop guard had a blind spot that let the above through.** `test/mcp-types.test.ts` modelled an SDK result as a flat `Record<string, unknown>`, which `McpToolResult` satisfies through its own explicit index signature, so the *value* of `structuredContent` was never checked against the narrower field type the SDK declares. The guard stayed green while every display helper was unassignable. It now also asserts against a stand-in that keeps the field shape, covering all five helpers and the typed-handler return position.
|
|
85
|
+
|
|
86
|
+
### Internal
|
|
87
|
+
|
|
88
|
+
- **`registeredComponentTypes()` on the renderer** returns every type the registry can render. The registry is the authority on that set, and nothing could read it, which is why a second copy existed to drift from. `test/component-types.test.ts` uses it to fail the build when the generated list and the registry disagree, in either direction, so a stale committed generated file cannot be merged.
|
|
89
|
+
- **One `escapeHtml` in `src/core/escape.ts` replaces three private copies** in `src/app.ts`, `src/mcp/resource.ts` and `src/renderer/components/typography.ts`, plus an `escapeAttr` in the first of those. The four escaped three different character sets (`& < > "`, `& < >`, and `& "`). Each was correct for its own context, and `resource.ts` was deliberately splitting text-content from attribute escaping, so this was not a live vulnerability, but nothing enforced any of it. The shared function covers `& < > " '`, which is safe in element text and in attribute values of either quote style, so callers no longer reason about context. Note for anyone narrowing it later: `renderMarkdownToHtml` escapes each line *before* parsing link and image URLs, so the up-front escape is what neutralizes `"` in `href`/`src`; a text-content-only escape there would open an attribute break-out. `test/escape.test.ts` pins the character set.
|
|
90
|
+
- Scoped the `consistent-type-definitions` exception per-declaration for the two wire types rather than per-file as `src/mcp/types.ts` does. `src/app.ts` and `src/mcp/display.ts` are mostly domain types that should stay interfaces, so flipping either file would invert the default for the wrong declarations. Moving the two types into `src/mcp/types.ts` was considered and rejected: that file has no imports by design, and `PrefabWireFormat` needs `ComponentJSON`, `ActionJSON`, `ColorMode` and `LayoutHints`, which would also make it cyclic with `src/app.ts`.
|
|
91
|
+
|
|
92
|
+
- **Staging CI is now the org pattern: push to `dev` keeps a promote PR open, merging it releases.** `auto-pr.yml` creates or refreshes a standing `dev → main` PR on every push to `dev`, and `release.yml` gained an `on: push: branches: [main]` trigger, so shipping is a one-click merge instead of a hand-written PR plus a manual `workflow_dispatch`. Copied from the sibling implementation in `Max-Health-Inc/mcp-http` rather than calling `Max-Health-Inc/.github`'s reusable `create-pr.yml`: that repo is private and this one is public, and GitHub does not let a public repo consume a reusable workflow from a private one. Tests are not duplicated into the PR workflow, since `ci.yml` already runs on the same push and reports onto the PR.
|
|
93
|
+
- **A merge that documents nothing now skips the release instead of failing it.** `release.yml` splits into a `gate` job and a `release` job; the gate uses the new `--check` flag on `scripts/changelog-release.ts`, which reports `promoted` / `present` / `missing` on stdout, writes nothing and always exits 0. A README or CI-only merge to `main` therefore leaves `main` green rather than red, while an explicit `workflow_dispatch` with an empty `[Unreleased]` still fails loudly, because asking for a release and having nothing to ship is an error. `--check` was added to `mcp-http`'s port of this script and is ported back here, where the script originated. Note the plain invocation was never a dry run: it rewrites `CHANGELOG.md` in place.
|
|
94
|
+
- **The release now merges itself back into `dev`.** The release commit promotes `[Unreleased]` to a version heading on `main`; leaving `dev` behind means its still-open `[Unreleased]` and `main`'s new heading differ only in where the heading sits, so git's line-based merge files `dev`'s newer entries under the *older* release heading. That silently attributes unshipped work to a published version and empties `[Unreleased]`, so the next merge skips its release. It happened between v0.3.6 and v0.3.7 and had to be repaired by hand. The sync step is best-effort and never fails a completed release; a conflict is reported in the run summary instead.
|
|
95
|
+
- Bumped `softprops/action-gh-release` to v3, which clears the Node 20 deprecation warning the v0.3.7 release run emitted.
|
|
96
|
+
- Bumped `actions/checkout` to v7 (six call sites across `auto-pr`, `ci`, `docs`, `publish` and `release`) and `actions/setup-node` to v7. Both were two majors behind. `setup-node` v7 drops its dummy `NODE_AUTH_TOKEN` export, which `publish.yml` never relied on: it publishes through OIDC trusted publishing (`id-token: write` plus `npx npm@latest publish --provenance`), the pattern that release documents. `oven-sh/setup-bun@v2` and `peaceiris/actions-gh-pages@v4` are on their current majors and unchanged.
|
|
97
|
+
- **Added `.github/dependabot.yml`, targeting `dev`.** Now that `release.yml` runs on `push: branches: [main]`, a Dependabot PR merged to `main` would cut a release for a routine bump, so the updates land on `dev` and ship through the standing promotion PR. Grouped per ecosystem, one PR a week each. Adapted from the sibling config in `mcp-http`, with a correction: TypeScript major bumps are ignored because `typescript-eslint@8.65.0` declares `peer typescript: >=4.8.4 <6.1.0` (verified against the installed tree, on `typescript` 6.0.3), and a grouped PR that fails every week would block the other dev-dependency updates. Worth knowing that the cap is `<6.1.0`, so even a minor bump to 6.1 falls outside the supported range.
|
|
98
|
+
|
|
99
|
+
### Docs
|
|
100
|
+
|
|
101
|
+
- **The npm package page now links to `https://maxhealth.tech/prefab` as its homepage.** `package.json` carried no `homepage`, so npm fell back to `repository` + `#readme` and sent every visitor to the raw README on GitHub instead of the documentation site. `bugs.url` is set alongside it, which npm was likewise deriving from `repository`. Both fields are read from the published tarball, so the listing only changes on the next release.
|
|
102
|
+
- Removed a stale README blockquote that restated the superset relationship six lines below the tagline already stating it, and pinned it to upstream `v0.19.1`. That pin predated 0.3.0's catch-up to upstream 0.20.x and contradicted the wire-format reference. The chart-formatting caveat it carried still lives in the Chart Formatting section, next to the props it describes.
|
|
103
|
+
|
|
104
|
+
## [0.3.7] — 2026-08-01
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- **The MCP protocol types could not be returned to an SDK handler without a cast.** Every shape in `src/mcp/types.ts` was an `interface`, and the SDK's result types are passthrough (`{ [x: string]: unknown }`). TypeScript grants an implicit index signature only to aliases of object types, never to interfaces, whose key set is not final because declaration merging can reopen them ([microsoft/TypeScript#15300](https://github.com/microsoft/TypeScript/issues/15300), closed as by-design), so returning a `McpResourceReadResult` from a `resources/read` handler failed with "Index signature for type 'string' is missing" — the one thing the type exists for. They are all type aliases now. `McpToolResult` was unaffected because it carries an explicit index signature. Verified against `@modelcontextprotocol/server` 2.0.0 rather than a stand-in, and guarded by `test/mcp-types.test.ts`, which fails `typecheck` (not at runtime) if any of them reverts to an interface.
|
|
109
|
+
- `McpTextResourceContents`, `McpBlobResourceContents`, `McpImageContent` and `McpResourceContent` are now exported from the package root, not just from `/mcp`. `McpResourceReadResult`'s default type parameter references the contents types, so naming it from a root import was impossible.
|
|
110
|
+
|
|
111
|
+
### Internal
|
|
112
|
+
|
|
113
|
+
- Scoped `consistent-type-definitions` to `'type'` for `src/mcp/types.ts` only. The repo default stays `'interface'`, which matches the prevailing TypeScript style and typescript-eslint's own recommendation; flipping all 151 interfaces in `src/` would be churn for a problem that only bites where a type must satisfy a foreign passthrough shape. Scoping it means the linter enforces the correct form for anything added to that file rather than pushing the next author into the bug and then into a per-line disable.
|
|
114
|
+
|
|
115
|
+
## [0.3.6] — 2026-07-31
|
|
116
|
+
|
|
117
|
+
### Fixed
|
|
118
|
+
|
|
119
|
+
- **A scheme set on the prefab container, rather than on `<html>`, now flips prefab's own tokens too.** `setThemeAttrs` themes the mount container (`applyMode` sets both it and `documentElement`, and `createThemeToggle({ syncDocument: false })` sets *only* the container), and the compiled wire theme has always matched at any depth via `.dark, [data-theme="dark"]`. But every scheme block in `prefab.css` was gated on `:root[…]`, so the two sheets disagreed about depth: a container-scoped toggle applied the theme's dark values over prefab's *light* `--background` and `--foreground`, a half-flipped panel. The base sheet's explicit dark and light blocks now carry the same three selectors the compiled theme emits (`:root[data-theme=…]`, the bare attribute, and the `dark`/`light` class), so both flip on the same element. The `:root[…]` branch is retained, so root-level behaviour and its specificity tie against the `prefers-color-scheme` block are unchanged; only elements below the root gain the tokens they were already asking for. Two schemes on one page work as a consequence.
|
|
120
|
+
- **Inline `Svg` authored without `xmlns` now renders.** `renderSvg` parses with `DOMParser` `image/svg+xml` (strict XML), where a missing `xmlns` produced namespace-less elements that rendered at 0×0. The renderer now injects `xmlns="http://www.w3.org/2000/svg"` when it's absent (`xmlns` is optional in HTML, so authors routinely omit it).
|
|
121
|
+
- The MCP Apps `ui/initialize` handshake reported a stale hard-coded app version (`0.2`) in `appInfo`. It now uses the real package `VERSION`, so a host sees the actual prefab version. (This is the *app/package* version, distinct from the `0.3` wire-format version in `$prefab.version` and the `2026-01-26` MCP Apps protocol version.)
|
|
122
|
+
|
|
123
|
+
- **Object values no longer render as `[object Object]`.** Every value that reaches a table cell, chart label, axis tick, tooltip title, heading, `{{ }}` interpolation or state-bound form input is typed `unknown` (it comes from JSON), and each site coerced it with a bare `String()`. Objects and arrays of objects therefore rendered as `[object Object]`. All of those sites now go through the new `stringifyValue` helper, which emits compact JSON for objects (honouring `toJSON()`), ISO strings for dates, and comma-joined members for arrays.
|
|
124
|
+
- **Identity comparisons on object values no longer collide.** `DataTable` row-selection highlighting, the `find:` pipe's key map and `Collection.firstKey()`/`lastKey()` used the same `String()` coercion to build lookup keys, so *every* object value produced the identical key `[object Object]` and matched every other object. They use `stringifyValue` now, which is injective for distinct JSON values.
|
|
125
|
+
|
|
126
|
+
- **Custom pipes now work in hosts whose CSP forbids eval.** Wire pipes arrive as function source and were always hydrated with `new Function()`, which throws in a sandboxed iframe or VS Code webview without `'unsafe-eval'`, so every custom pipe silently degraded to the raw value. A pipe that is already registered locally (by a companion script loaded through `rendererHtml({ scripts })`) is now kept instead of being re-evaluated, so pre-registering is all it takes. The eval failure is also reported once per mount with an actionable message, rather than once per pipe as an opaque `EvalError`.
|
|
127
|
+
- **`registerViewerResource` emits the cache fields required by protocol revision 2026-07-28.** `resources/read` results extend `CacheableResult` (SEP-2549), and a result without `ttlMs`/`cacheScope` falls back to `{ ttlMs: 0, cacheScope: 'private' }`, meaning the viewer was re-fetched on every render. The viewer HTML is a pure function of the package version (the CDN base pins it), so it now ships `{ ttlMs: 86_400_000, cacheScope: 'public' }`, overridable via the new `cache` option and validated so an out-of-range value throws instead of being silently discarded.
|
|
128
|
+
- **`registerViewerResource` prefers `registerResource()` over the retired `resource()` overload.** `resource()` is deprecated in SDK v1 and gone in `@modelcontextprotocol/server` v2; `resource()` is now only a fallback, and a server exposing neither raises a clear `TypeError`.
|
|
129
|
+
|
|
130
|
+
### Added
|
|
131
|
+
|
|
132
|
+
- **`rendererHtml({ themeBridge: 'vscode' })` and the same option on `registerViewerResource`.** `prefab.css` resolves each token as MCP Apps host variable, then VS Code webview variable, then static default, so a host that defines the MCP Apps `--color-*` variables shadows the VS Code ones entirely and the viewer stops following the user's editor theme. The bridge emits a `<style>` block after `prefab.css` re-declaring the affected tokens with `--vscode-*` first and the MCP Apps layer dropped, keeping prefab's own fallbacks so nothing changes outside VS Code. Head order is the contract: base theme, bridge, then the caller's `stylesheets`. It covers `:root` and the `prefers-color-scheme: dark` block but deliberately not `:root[data-theme="dark"]`, which is the standalone dark palette (host theming cascades, the manual toggle does not). `VSCODE_BRIDGE` is the exported mapping table, and `test/theme-bridge.test.ts` cross-checks every entry against `prefab.css` so the bridge cannot drift from the theme it overrides.
|
|
133
|
+
- `registerViewerResource` declares the `io.modelcontextprotocol/ui` extension capability (SEP-2133) on the server, since MCP Apps is an extension to the core protocol rather than part of it. Opt out with `declareCapability: false`. Registering on an already-connected server logs a warning instead of throwing, and still registers the resource. (This entry said `…/apps` while the constant held that value; both are corrected. `…/ui` is what the MCP Apps spec defines and what `ext-apps` exports as `EXTENSION_ID`, so the old value declared the capability under a key no host looks up. Never published, so there is nothing to migrate.)
|
|
134
|
+
- `toolResult(payload)` builds the MCP tool-result envelope (`content[0].text` plus `structuredContent`) that the `display_*` helpers return, exported for handlers that already have wire JSON and were hand-rolling the same shape.
|
|
135
|
+
- New MCP protocol types: `McpCacheScope`, `McpCacheHint`, `McpResourceReadResult`, `ResourceConfig`, `ResourceReadHandler`, and `MCP_APP_MIME` / `APPS_EXTENSION` / `DEFAULT_VIEWER_CACHE` constants.
|
|
136
|
+
|
|
137
|
+
### Internal
|
|
138
|
+
|
|
139
|
+
- Tracked `brandc` 0.6.0, which stops `@property`-registering the scheme-dependent colour tokens so their `light-dark()` resolves against the element that reads it rather than once at `:root` (max-network/brandc#14). That only changes `toCss` output, which prefab does not consume — it compiles its own CSS from the wire `theme` — so the drift guard passes unchanged. Worth knowing anyway, because `setThemeAttrs` accepts any element: a host that themes a container rather than `documentElement` now gets the subtree themed correctly if it loads brandc's stylesheet.
|
|
140
|
+
- Added `stringifyValue` (`src/core/stringify.ts`, exported from the package root) as the single value-to-string coercion for display and identity, replacing 34 ad-hoc `String(unknown)` call sites across 11 modules.
|
|
141
|
+
- Bumped dev tooling: `eslint` 10.8.0, `typescript-eslint` 8.65.0, `happy-dom` 20.11.1, `@types/bun` 1.3.14, `jiti` 2.7.0, `typedoc` 0.28.20, `vitepress-plugin-llms` 1.13.4. The `typescript-eslint` bump is what surfaced the `String(unknown)` and `cacheScope` defects above, via `no-base-to-string` and `no-unnecessary-condition`.
|
|
142
|
+
- Centralized `VERSION` and `PROTOCOL_VERSION` into `src/core/version.ts` (a zero-import single source of truth) so the lean renderer bundle can read the version without pulling in the builder. `release.yml` now bumps that file.
|
|
143
|
+
- `structuredContent` on `McpToolResult` is now a type parameter carrying the payload type (SEP-2106 loosened the field to any JSON value), which removed the five `as unknown as Record<string, unknown>` casts in the display helpers and lets callers read the wire back without casting.
|
|
144
|
+
- Split `src/mcp/display.ts` (606 lines) into `display.ts` (the builders), `resource.ts` (`ui://` registration, CSP meta, viewer HTML) and `result.ts` (the shared envelope). All five `display_*` builders constructed the same envelope inline; they now share one, so protocol-level result fields have a single home.
|
|
145
|
+
- Confirmed `resultType` (SEP-2322) is not ours to set: `@modelcontextprotocol/server` treats it as a wire-only key, stamps it at its 2026-era encode seam and strips it before results reach consumers, which is why its public result types omit it. The one handler-authored case is `resultType: 'input_required'` for multi-round-trip results, which no display helper produces.
|
|
146
|
+
- Tracked `brandc` 0.5.0, which declares the token contract explicitly instead of deriving it from one brand (max-network/brandc#13, filed from prefab's side while writing the guard below). The guard now iterates the exported `BRANDS` rather than naming brands, splits scalars from colours using `CONTRACT_SCALARS`, and adds two checks the new API makes possible: prefab reads no token that left the contract (`DEPRECATED_TOKENS`) and none of any brand's private extras (`brandExtras`), either of which would tie prefab to a single brand.
|
|
147
|
+
- Pinned prefab's design tokens to the shared `brandc` contract with a drift guard (`test/brand-contract.test.ts`). All 30 token names `prefab.css` defines are already contract names, so the test asserts that and the reverse direction: every token prefab reads is covered by each shipped brand, in `:root` for scalars and in both schemes for colours. brandc is a devDependency only. prefab keeps zero runtime dependencies, and the token *values* stay as host-adaptive `var()` fallback chains (MCP Apps var, then VS Code var, then static default) rather than adopting brandc's literal `oklch()` values, which would break host theming and change the default look. `toPrefabTheme()` already emits prefab's exact wire `theme` shape, so a brand drives prefab with no adapter.
|
|
148
|
+
- `test/fixtures/run_fixtures.py` skips regeneration with a notice when the `prefab_ui` Python package is absent, instead of failing and blocking `bun run test`. Its status output is also written as UTF-8, since a cp1252 Windows console raised `UnicodeEncodeError` from inside the error handler and masked the real failure.
|
|
149
|
+
|
|
150
|
+
### Docs
|
|
151
|
+
|
|
152
|
+
- Rewrote the playground example descriptions in plain language for first-time viewers, dropping internal jargon (`Define`/`Use`/`Condition`, `display_update`, "mock MCP tool", `onChange`/`setState`, "wire format") in favor of what the user sees and does. The Composition example is restructured into two labeled sections with natural labels ("Grade:", "Note:") instead of "Condition:" / "If / Elif / Else:".
|
|
153
|
+
|
|
154
|
+
## [0.3.5] — 2026-07-22
|
|
155
|
+
|
|
156
|
+
### Fixed
|
|
157
|
+
|
|
158
|
+
- `validateWireFormat` no longer false-flags legitimate component slots. `trigger` (Dialog), `empty` (Detail/MasterDetail), `else` (Condition), and `summary` (ExpandableRow) are read by the renderer as component nodes, so they are no longer reported as misplaced children.
|
|
159
|
+
- **`Popover` and `HoverCard` now accept a `trigger` component** (like `Dialog`). Previously they rendered all children into hidden content with no visible trigger, so there was nothing to click or hover. The `trigger` is rendered as the always-visible target; `children` are the content.
|
|
160
|
+
- **`Slot` now receives injected content from `Use`.** A `Use` node's own `children` fill the default slot, and a `slots` map fills named slots, so a `Slot` inside a `Define` template renders the caller's content instead of only its fallback.
|
|
161
|
+
|
|
162
|
+
### Docs
|
|
163
|
+
|
|
164
|
+
- Added playground showcase examples: **Chart Gallery** (all six chart types as native SVG), **Analytics Dashboard** (KPI metrics, charts, tabs, and range controls driving reactive state), and **Interactive Components** (Dialog, Accordion, and Switch/Slider bound to a live readout).
|
|
165
|
+
- Added **MCP app examples** with an in-page mock MCP server, so the playground demonstrates the round-trip (not just on-demand rendering): **MCP: Tool Call** (`callTool` → `display_update`, server-authoritative counter), **MCP: Form → Tool** (form submit → tool → updated list), and **MCP: Live Data** (`Subscribe` with polling fallback → live metrics and chart). Modeled on the `_action` + `chess://` subscription pattern from real prefab MCP apps.
|
|
166
|
+
- Added coverage-filling examples that raise demonstrated component coverage from ~37% to ~60%: **Master / Detail** (`MasterDetail` + `Detail` + selection), **Form Controls** (`RadioGroup`, `Combobox`, `DatePicker`, `Switch`, `Slider` with a live readout), **Composition & Control Flow** (`Define`/`Use` template reuse + `Condition` + `If`/`Elif`/`Else`), and **Content & Media** (`Markdown`, `Code`, `Kbd`, inline `Svg`, `Ring`, `Sparkline`, `RadialChart`, `Histogram`, `Tooltip`, `Carousel`).
|
|
167
|
+
- Corrected the playground's AI system prompt: it now teaches the `0.3` wire format, the real component and action set, control flow, and pipes, replacing the stale `0.2` guidance (`showToast` `title`, `appendState` `item`, and non-existent components).
|
|
168
|
+
|
|
169
|
+
### Internal
|
|
170
|
+
|
|
171
|
+
- Expanded regression coverage with TDD probes across formatting pipes, expressions, state ops, `ForEach`, the semantic `Table`, `DataTable` interactions (accessor, row click, search), and chart edge cases (empty data, single point), alongside tests for the `Popover`/`HoverCard`/`Slot` fixes above.
|
|
172
|
+
|
|
173
|
+
## [0.3.4] — 2026-07-22
|
|
174
|
+
|
|
175
|
+
### Fixed
|
|
176
|
+
|
|
177
|
+
- Action dispatch failures are no longer silent. Every fire-and-forget handler (`onClick`, `onMount`, `onChange`, `onSubmit`, `onRowClick`, keyboard bindings, interval ticks, and `display_update` actions) now routes rejections through the logger via a new `fireAndForget` helper, so a throwing action surfaces instead of vanishing. The async actions (`toolCall`, `fetch`, `callHandler`, `subscribe`) report through a shared `reportActionError`: at `debug` when an `onError` handler is wired, at `warn` when none is, so an unhandled failure is never fully quiet.
|
|
178
|
+
|
|
179
|
+
### Internal
|
|
180
|
+
|
|
181
|
+
- The release workflow now requires a changelog entry: it fails if `## [Unreleased]` has no entries for the version about to ship, and otherwise promotes that section to the new version heading (`scripts/changelog-release.ts`). This keeps the changelog from drifting behind published releases.
|
|
182
|
+
|
|
183
|
+
## [0.3.3] — 2026-07-22
|
|
184
|
+
|
|
185
|
+
### Added
|
|
186
|
+
|
|
187
|
+
- **Centralized logger** (`src/core/logger.ts`), zero runtime dependencies. Mirrors the `@maxhealth.tech/utils` `createLogger` contract (scoped `error`/`warn`/`info`/`debug`, `[prefab]` / `[prefab:scope]` prefix) and adds a runtime `setLogLevel()` so an embedding host can mute or raise prefab's console output. All internal `console.*` calls now route through it. New exports: `createLogger`, `log`, `setLogLevel`, `getLogLevel`, `LogLevel`, `Logger` (and `window.prefab.setLogLevel`).
|
|
188
|
+
- **Wire validation now catches silent authoring mistakes.** `validateWireFormat` flags child components placed under a non-`children` key (e.g. `then`/`else`/`body`), which the renderer never reads, and requires a non-empty `message` on `showToast`. `PrefabRenderer.mount()` runs validation and reports any problems as non-fatal console warnings (opt out with `{ validate: false }`), so a payload that renders nothing now tells you why. `validateWireFormat` / `isValidWireFormat` are also exported from the renderer entry.
|
|
189
|
+
|
|
190
|
+
### Fixed
|
|
191
|
+
|
|
192
|
+
- **DataTable renders primitive rows.** A table over an array of strings or numbers rendered blank cells, because `row[column.key]` is undefined on a primitive. A primitive row is now used directly as the cell value, so a plain string list displays.
|
|
193
|
+
|
|
194
|
+
### Docs
|
|
195
|
+
|
|
196
|
+
- Repaired the playground examples, each verified against the live renderer: Conditional UI declared its branches under a non-existent `then` key (renamed to `children`), Todo List could not display its string tasks through a DataTable (now a `ForEach` list), and the Contact form toast passed `title` instead of `message`. The playground's own toast handler read `t.title` (never present on `ToastEvent`) and now reads `t.message`. All example payloads bumped to wire version `0.3`.
|
|
197
|
+
- Reframed the project tagline across the README and the site description: from a declarative UI component library "wire-compatible with" PrefectHQ's Python `prefab-ui` to a full-stack framework and a superset of it.
|
|
198
|
+
|
|
199
|
+
## [0.3.2] — 2026-07-19
|
|
200
|
+
|
|
201
|
+
### Fixed
|
|
202
|
+
|
|
203
|
+
- **Badge no longer stretches to full width.** `.pf-badge` is `inline-flex`, but as a flex child it was blockified and stretched to the container's cross size (full width inside a column, full height inside a row). It now uses `align-self: flex-start` to stay intrinsically sized; an explicit `w-full` or width utility still overrides it.
|
|
204
|
+
|
|
205
|
+
### Docs
|
|
206
|
+
|
|
207
|
+
- **Playground preview text contrast.** The mounted preview inherited the playground chrome's light-gray text color, leaving content near-invisible on a light preview. The preview now applies the theme's `--foreground`.
|
|
208
|
+
- **Single-sourced the playground theme state.** The toolbar "Dark" checkbox and the renderer's floating theme toggle were independent (one toggled a class, the other set `data-theme` plus localStorage and document sync) and drifted apart. They now share one source of truth. The demo and playground CDN example pins were bumped from `@0.2` to `@0.3`.
|
|
209
|
+
|
|
210
|
+
## [0.3.1] — 2026-06-27
|
|
211
|
+
|
|
212
|
+
Documentation and tooling release. No runtime or API changes: `src/` is unchanged aside from the version bump, so the published bundle is functionally identical to 0.3.0.
|
|
213
|
+
|
|
214
|
+
### Fixed
|
|
215
|
+
|
|
216
|
+
- Corrected the wire-protocol version label across the user-facing docs. The README badge and tagline, the skill header, the homepage feature text, and a version-pin example still advertised `0.2`, but the library has shipped protocol `0.3` since 0.3.0 (the renderer still accepts legacy `0.2` payloads).
|
|
217
|
+
|
|
218
|
+
### Docs
|
|
219
|
+
|
|
220
|
+
- Deduplicated the Guide and Reference sections. Each reference topic now lives in one place, and the Guide pages are concise conceptual pages that link into the reference, removing a large amount of duplicated content.
|
|
221
|
+
- Single-sourced the example UIs. The demo and playground now load one canonical set of `$prefab` examples from `docs/public/examples/` at runtime instead of embedding their own copies, and the skill bundle's example assets are generated from that same set.
|
|
222
|
+
- The programmatic API reference (actions, rx, mcp, auto, renderer) is now generated from the source TSDoc with TypeDoc, so it stays in sync with the code. The component catalog, wire format, and MCP Apps pages remain hand-written.
|
|
223
|
+
|
|
224
|
+
## [0.3.0] — 2026-06-16
|
|
225
|
+
|
|
226
|
+
### Wire Format (Breaking) — protocol `0.3`
|
|
227
|
+
|
|
228
|
+
Catches up to upstream PrefectHQ/prefab v0.20.x ("Wire Transfer", PR #431). The `$prefab.version` envelope is now **`0.3`**. The renderer still accepts `0.2` payloads, so existing stored wire data keeps rendering.
|
|
229
|
+
|
|
230
|
+
- **New top-level `css` field** — an array of inline CSS blocks, injected as `<style>` tags. The **theme is now compiled into this array** (`:root` for light, `.dark, [data-theme="dark"]` for dark) instead of shipping a structured `theme` object; `PrefabApp.toJSON()` no longer emits a `theme` key. New `compileThemeCss()` / `ThemeVars` exports expose the compiler.
|
|
231
|
+
- **`stylesheets` redefined as external URLs** — rendered as `<link rel="stylesheet" href="…">`. The renderer keeps a heuristic that still injects inline CSS found in `stylesheets` as `<style>` for backward compatibility with `0.2` payloads.
|
|
232
|
+
- **New top-level `mode` field** (`'light' | 'dark'`) — forces a color scheme. The renderer applies it via both the `data-theme` attribute and the `dark`/`light` class (so upstream-compiled `.dark {}` CSS resolves), and `toHTML()` stamps it on `<html>`.
|
|
233
|
+
- **`PrefabApp` / `DisplayOptions`** gain `css` and `mode` options; `display()` merges `css` (concatenated) and `mode` (override) like the other options.
|
|
234
|
+
- `toHTML()` now emits `css`/`stylesheets`/`mode` into `<head>` and strips them from the embedded JSON to avoid double-injection (mirrors upstream `html()`).
|
|
235
|
+
|
|
236
|
+
### Charts
|
|
237
|
+
|
|
238
|
+
- **`valueFormat` parity (PR #454)** — added the canonical `valueFormat` prop to cartesian/pie/radial charts (value-axis ticks + tooltip values), matching upstream's field name so chart formats are wire-compatible in both directions. `yAxisFormat` / `yAxisRightFormat` remain TS-only overrides for dual-axis charts. The renderer reads `valueFormat` as the base format and treats `"auto"` as "no explicit format".
|
|
239
|
+
- **Pie/Radial/Scatter data-binding parity** — `PieChart` and `RadialChart` now use upstream's `dataKey` (value) + `nameKey` (label) model, and `ScatterChart` gains `xAxis`/`yAxis`/`zAxis` (z = bubble size). The legacy `series`/`xAxis` inputs are still accepted and mapped onto the canonical fields (**dual-accept**, non-breaking), and the Pie renderer reads both shapes — so these charts now round-trip with the Python renderer. New exports: `CategoricalChartProps`, `PieChartProps`, `ScatterChartProps`.
|
|
240
|
+
- **Native Radial, Scatter & Radar renderers** — replaced the "not yet supported" placeholders with real SVG renderers, so **every chart type now draws natively** (the fallback is gone):
|
|
241
|
+
- `RadialChart` — concentric value arcs (muted track + coloured value), configurable `innerRadius`/`startAngle`/`endAngle`.
|
|
242
|
+
- `ScatterChart` — points over min/max axes with optional grid, tooltips, and **bubble sizing** from `zAxis`.
|
|
243
|
+
- `RadarChart` — one polygon per series over angular axes, with `axisKey` spoke labels, `filled`, and `showDots`; legacy `xAxis` maps to `axisKey`.
|
|
244
|
+
- All honour `valueFormat`.
|
|
245
|
+
- **Refactor** — the chart renderers' shared toolkit (colours, axes, SVG primitives, value formatting, legend) was extracted into `chart-helpers.ts` to keep each renderer file under the 700-LOC limit.
|
|
246
|
+
|
|
247
|
+
### Internal
|
|
248
|
+
|
|
249
|
+
- **DRY theme compilation** — `applyTheme()` (the legacy `theme`-object path) now routes its dark block through the same `compileThemeCss()` the wire path uses, so both paths emit byte-identical dark CSS. The CSS sanitizers are shared too.
|
|
250
|
+
|
|
251
|
+
> **Fixtures verified against upstream.** Golden fixtures were regenerated from upstream `prefab-ui` **0.20.2**: 9/10 are byte-identical to the previous `0.2` fixtures apart from the version bump (confirming parity, and that upstream emits `0.3`). The 10th (chart) now carries upstream's `valueFormat`, which the builder also emits.
|
|
252
|
+
|
|
253
|
+
## [0.2.40] — 2026-05-15
|
|
254
|
+
|
|
255
|
+
### New Features
|
|
256
|
+
- **`onHostContextChanged` callback** — `PrefabApp` now exposes `onHostContextChanged((ctx) => {...})` so components can react to arbitrary fields in `ui/notifications/host-context-changed` (e.g. refreshed access tokens, locale, custom app config). The existing `prefab:theme-update` event continues to fire for backward-compat theme handling (closes #14)
|
|
257
|
+
- **`prefab:host-context-changed` bridge event** — the bridge now dispatches the full params object from `host-context-changed` as a separate `prefab:host-context-changed` event, in addition to the existing `prefab:theme-update`
|
|
258
|
+
|
|
259
|
+
## [0.2.39] — 2026-05-10
|
|
260
|
+
|
|
261
|
+
### New Features
|
|
262
|
+
- **`display_update()` actions** — `display_update()` now accepts an optional `actions` parameter to fire actions alongside state deltas. Actions execute after the state is merged into the store (closes #13)
|
|
263
|
+
```ts
|
|
264
|
+
display_update(
|
|
265
|
+
{ 'game.turn': 'b', 'game.fen': '...' },
|
|
266
|
+
{ actions: [new SendMessage("Your turn. Pick a move.")] }
|
|
267
|
+
)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## [0.2.38] — 2026-05-09
|
|
271
|
+
|
|
272
|
+
### Bug Fixes
|
|
273
|
+
- **Fixed**: `display()` now merges options into an existing `PrefabApp` instance — previously `state`, `stylesheets`, `theme`, `layout`, `pipes`, and all other options were silently discarded when a `PrefabApp` was passed. State and defs are shallow-merged (options win on conflict), stylesheets are concatenated, and scalar options (theme, layout, cssClass, onMount) fall back to the app's value (closes #12)
|
|
274
|
+
|
|
275
|
+
## [0.2.37] — 2026-05-09
|
|
276
|
+
|
|
277
|
+
### New Features
|
|
278
|
+
- **Reactive `cssClass`** — `cssClass` now accepts `RxStr` (reactive expressions) on all components. Use `rx()` to dynamically set CSS classes based on state (closes #11 part 1)
|
|
279
|
+
- **`onClick` on all components** — `Div`, `Span`, `Column`, `Row`, `Grid`, `GridItem`, `Container`, and all other components now support `onClick` actions. Non-button elements automatically get `role="button"`, `tabindex="0"`, and keyboard (Enter/Space) support for accessibility (closes #11 part 2)
|
|
280
|
+
|
|
281
|
+
## [0.2.36] — 2026-05-09
|
|
282
|
+
|
|
283
|
+
### Bug Fixes
|
|
284
|
+
- **Fixed**: Subscribe `onDataCallback` no longer clobbers merged state with raw response data — when `display_update()` merges a state delta via `applyPrefabUpdate()`, `store.set(stateKey, data)` is now skipped, preventing `stateKey` collision with delta keys (closes #10)
|
|
285
|
+
- **Fixed**: ESLint config now ignores `docs/` (VitePress cache/dist) and `eslint.config.ts` — previously caused 60 spurious parsing errors
|
|
286
|
+
|
|
287
|
+
## [0.2.35] — 2026-05-09
|
|
288
|
+
|
|
289
|
+
### Bug Fixes
|
|
290
|
+
- **Fixed**: `display_form()` now forwards all `DisplayOptions` — `layout`, `cssClass`, `stylesheets`, `pipes`, `onMount`, `keyBindings`, and `defs` were silently dropped (only `state` and `theme` worked). `DisplayFormOptions` now extends `DisplayOptions`
|
|
291
|
+
- **Fixed**: `display_success` / `displaySuccess` and `DisplaySuccessOptions` are now exported from the package entry point
|
|
292
|
+
|
|
293
|
+
## [0.2.34] — 2026-05-09
|
|
294
|
+
|
|
295
|
+
### Bug Fixes
|
|
296
|
+
- **Fixed**: Subscribe fallback poll handler now detects `$prefab` responses — poll results containing full views trigger `remount()`, and `display_update` payloads merge state into the store. Previously poll results were stored as raw data, leaving the DOM frozen (closes #9)
|
|
297
|
+
|
|
298
|
+
## [0.2.33] — 2026-05-09
|
|
299
|
+
|
|
300
|
+
### Bug Fixes
|
|
301
|
+
- **Fixed**: `display()` now forwards `stylesheets` and `pipes` options to `PrefabApp` — previously these were silently dropped since `DisplayOptions` didn't include them (closes #8)
|
|
302
|
+
|
|
303
|
+
## [0.2.32] — 2026-05-09
|
|
304
|
+
|
|
305
|
+
### Bug Fixes
|
|
306
|
+
- **Fixed**: `If()` / `Elif()` shorthand now correctly detects `Rx` expression objects — the previous inline type check only matched strings and `Ref` (subscribe), silently treating `Rx({ expression })` as a props object
|
|
307
|
+
- **Fixed**: Action handlers (`toolCall`, `callHandler`, `fetch`) now handle `display_update()` state delta payloads (`{ $prefab, update: { state } }`) by merging into the store — previously these were silently dropped while `onToolResult` in auto-mount already handled them correctly (closes #7)
|
|
308
|
+
|
|
309
|
+
## [0.2.31] — 2026-05-09
|
|
310
|
+
|
|
311
|
+
### New Features
|
|
312
|
+
- **CallTool structuredContent remount** — when a `toolCall`, `callHandler`, or `fetch` action returns a full prefab view (`{ $prefab, view }`), the renderer remounts with the new view instead of just storing the raw result (#7)
|
|
313
|
+
- Supports direct payloads, MCP `structuredContent` wrappers, and `content[].text` JSON blocks
|
|
314
|
+
- Preserves existing store state across remounts
|
|
315
|
+
- Handles pipes, stylesheets, layout hints, key bindings, defs, and theme on remount
|
|
316
|
+
|
|
317
|
+
## [0.2.30] — 2026-05-09
|
|
318
|
+
|
|
319
|
+
### Bug Fixes
|
|
320
|
+
- **Fixed**: Reverted serializer output back to **camelCase** (`cssClass`, `onMount`, `onClick`, `resultKey`, etc.), matching the upstream PrefectHQ/prefab wire format (`by_alias=True`). The snake_case serializer in v0.2.28 was a breaking change based on an incorrect assumption about the wire spec.
|
|
321
|
+
- **Kept**: Renderer normalization layer — `renderNode()` and `dispatchOne()` accept **both** snake_case and camelCase input, so Python-SDK-generated wire data still renders correctly.
|
|
322
|
+
- **Fixed**: `autoTable` column keys now match raw data keys (no `toCamelCase` conversion) — snake_case row keys like `proposed_start` render correctly without column/key mismatch.
|
|
323
|
+
- **Fixed**: `cdnBase()` now uses the **exact** version (`@0.2.30`) instead of a semver range (`@0.2`) — eliminates stale jsDelivr cache serving old renderer bundles after a new patch publish (closes #6)
|
|
324
|
+
|
|
325
|
+
## [0.2.29] — 2026-05-09 *(unpublished — included the faulty snake_case serializer from v0.2.28)*
|
|
326
|
+
|
|
327
|
+
### Bug Fixes
|
|
328
|
+
- **Fixed**: `rendererHtml()` CDN URL now derives major.minor from `VERSION` automatically — no more hardcoded `@0.2` constant that would go stale on a minor/major bump
|
|
329
|
+
|
|
330
|
+
## [0.2.28] — 2026-05-09 *(unpublished — snake_case serializer was a breaking change, reverted in v0.2.30)*
|
|
331
|
+
|
|
332
|
+
### New Features
|
|
333
|
+
- **`Subscribe` action** — real-time resource updates via `subscribe(uri, stateKey, opts)` with automatic fallback polling when the host doesn't support MCP `notifications/resources/updated` (#3)
|
|
334
|
+
- Supports `fallbackInterval`, `fallbackTool`, `fallbackArgs` for polling-based hosts
|
|
335
|
+
- `onData` / `onError` callbacks for reactive state binding
|
|
336
|
+
- **`PdfViewer` component** — embed PDF documents with `pdfViewer(src, opts)` (#2)
|
|
337
|
+
|
|
338
|
+
### ~~Wire Format (Breaking)~~
|
|
339
|
+
- ~~**snake_case wire format** — all structural keys in the `$prefab` JSON output are now snake_case~~ *(reverted in v0.2.30)*
|
|
340
|
+
- Renderer normalizes incoming JSON at entry points (`renderNode`, `dispatchOne`), accepting both snake_case and camelCase input for backwards compatibility
|
|
341
|
+
- User-data containers (`state`, `arguments`, `context`, `overrides`) are **not** converted — keys inside them are preserved as-is
|
|
342
|
+
|
|
343
|
+
### Bug Fixes
|
|
344
|
+
- **Fixed**: `fallbackArgs` reactive expressions now resolve correctly in Subscribe action
|
|
345
|
+
- **Fixed**: resilient cleanup for Subscribe timers and listeners
|
|
346
|
+
- **Fixed**: chart series structural keys (`dataKey` → `data_key`) now correctly serialized
|
|
347
|
+
- **Fixed**: `PrefabApp.toJSON()` root view outputs `css_class` (was bypassing `Component.toJSON()`)
|
|
348
|
+
- **Fixed**: wire format validator accepts both camelCase and snake_case action prop names
|
|
349
|
+
|
|
350
|
+
### Documentation
|
|
351
|
+
- SEO & LLM visibility improvements for documentation site
|
|
352
|
+
|
|
353
|
+
### Tests
|
|
354
|
+
- 22 new TDD tests for snake_case serialization and renderer normalization
|
|
355
|
+
- All 10 golden fixtures updated to snake_case wire format
|
|
356
|
+
- **1237 tests** passing across 36 files
|
|
357
|
+
|
|
358
|
+
## [0.2.27] — 2026-05-02
|
|
359
|
+
|
|
360
|
+
### Bug Fixes
|
|
361
|
+
- **Fixed**: `resourceMeta()` now serializes permissions as `{}` (empty objects) per the MCP Apps spec, not `true`. The DX-facing input type stays `boolean` for ergonomics; the wire output is now spec-compliant with `McpUiResourcePermissions`
|
|
362
|
+
|
|
363
|
+
## [0.2.26] — 2026-05-02
|
|
364
|
+
|
|
365
|
+
### New Features
|
|
366
|
+
- **`rendererHtml(opts?)`** — generates the viewer HTML page string for MCP Apps resources (loads `prefab.css` + `renderer.auto.min.js` from CDN, with optional extra scripts/stylesheets)
|
|
367
|
+
- **`registerViewerResource(server, opts?)`** — one-liner to register a `ui://` viewer resource with correct MIME type, CSP (auto-merged with jsdelivr default + script origins), Permission Policy, and `_meta` on both listing and content item
|
|
368
|
+
- **`PREFAB_RESOURCE_URI`** — default URI constant (`'ui://prefab/viewer'`)
|
|
369
|
+
- All three exported from both `@maxhealth.tech/prefab/mcp` and the main barrel
|
|
370
|
+
|
|
371
|
+
### Documentation
|
|
372
|
+
- Corrected CSP/permissions claims about Claude Desktop (it DOES enforce CSP via HTTP headers on `{hash}.claudemcpcontent.com`)
|
|
373
|
+
- Corrected cross-server tool call claim (spec says `"app": Tool callable by the app from this server only`)
|
|
374
|
+
- Updated host compatibility table (all hosts enforce CSP)
|
|
375
|
+
- Added `registerViewerResource()` shortcut section to `docs/reference/mcp-apps.md`
|
|
376
|
+
- Added `rendererHtml()`, `registerViewerResource()`, `PREFAB_RESOURCE_URI` reference docs to `docs/reference/mcp-display.md`
|
|
377
|
+
|
|
378
|
+
### Tests
|
|
379
|
+
- 15 new tests for `rendererHtml()`, `registerViewerResource()`, `PREFAB_RESOURCE_URI` (security escaping, CSP merging, handler invocation)
|
|
380
|
+
- Comprehensive layout hints test coverage (bridge, renderer combos, passthrough, HTML/MCP round-trip)
|
|
381
|
+
- **1174 tests** passing across 32 files
|
|
382
|
+
|
|
383
|
+
## [0.2.25] — 2026-04-30
|
|
384
|
+
|
|
385
|
+
### Layout Hints
|
|
386
|
+
- **New**: `LayoutHints` type — `preferredHeight`, `minHeight`, `maxHeight` (px) for declarative host container sizing
|
|
387
|
+
- `PrefabWireFormat.layout` — new optional field in the `$prefab` wire format
|
|
388
|
+
- `PrefabAppOptions.layout` — set directly on the app constructor
|
|
389
|
+
- `DisplayOptions.layout` — set via `display(view, { layout: { preferredHeight: 600 } })`
|
|
390
|
+
- Renderer applies `height` / `min-height` / `max-height` + `overflow: auto` as inline styles on the mount root
|
|
391
|
+
- `Bridge.notifyPreferredSize()` — emits `ui/notifications/preferred-size` (JSON-RPC) or `prefab:preferred-size` (prefab protocol) to the host on mount
|
|
392
|
+
- Auto-mount (`renderer.auto.min.js`) forwards layout hints automatically when wire data contains a `layout` field
|
|
393
|
+
|
|
394
|
+
### CSS Utilities
|
|
395
|
+
- **Added**: `max-h-*` utility classes (`max-h-48`, `max-h-64`, `max-h-80`, `max-h-96`, `max-h-full`, `max-h-screen`, `max-h-none`)
|
|
396
|
+
|
|
397
|
+
### Bug Fixes
|
|
398
|
+
- **Fixed**: `DOM.Iterable` added to tsconfig `lib` — resolves `NodeListOf` iteration errors in strict mode
|
|
399
|
+
- **Fixed**: null-safe `.textContent` access in `data.ts` (DataTable search) and `form.ts` (Combobox filter)
|
|
400
|
+
- **0 tsc errors**, **1149 tests** passing across 32 files
|
|
401
|
+
|
|
402
|
+
## [0.2.24] — 2026-04-30
|
|
403
|
+
|
|
404
|
+
### Utility Classes
|
|
405
|
+
- **Added**: ~200 Tailwind-compatible utility classes in `prefab.css` — auto-renderers no longer require Tailwind CSS
|
|
406
|
+
- Categories: display, flexbox, gap, padding (px/py/pt/pb/pl/pr scales), margin (mx/my/mt/mb/ml/mr scales), width, height, max-width (xs–7xl), typography (text-xs–4xl, font weights, alignment, line-height, tracking), theme-aware text/bg colors, borders, rounded, shadows, overflow, position, z-index, opacity, cursor, transitions, sr-only
|
|
407
|
+
|
|
408
|
+
### Theme Fix
|
|
409
|
+
- **Fixed**: reverted to v0.2.20 theme behaviour after regressions in v0.2.21/v0.2.22
|
|
410
|
+
- `applyHostTheme()` sets `data-theme` from host `colorScheme` again — toggle works correctly in hosted mode
|
|
411
|
+
- `syncVsCodeTheme()` restored for standalone VS Code webviews (reads `data-vscode-theme-kind`, MutationObserver)
|
|
412
|
+
- `[data-theme]` blocks use static values only (no host var references) — prevents both blocks resolving identically
|
|
413
|
+
|
|
414
|
+
## [0.2.22] — 2026-04-29
|
|
415
|
+
|
|
416
|
+
### Host Theme Adaptation
|
|
417
|
+
- Separated host theming from toggle: `applyHostTheme()` no longer sets `data-theme`
|
|
418
|
+
- Removed `syncVsCodeTheme()` (later restored in v0.2.23)
|
|
419
|
+
|
|
420
|
+
## [0.2.21] — 2026-04-29
|
|
421
|
+
|
|
422
|
+
### Host Theme Adaptation
|
|
423
|
+
- **Fixed**: hosted mode (Claude Desktop / VS Code) now adapts to host theme colors correctly
|
|
424
|
+
- `applyHostTheme()` no longer sets `data-theme` — host's inline CSS variables resolve through the `var()` fallback chain in `:root` / `@media dark` blocks automatically
|
|
425
|
+
- Removed `syncVsCodeTheme()` — VS Code's `--vscode-*` vars resolve through the same chain without needing `data-theme`
|
|
426
|
+
- `[data-theme]` blocks use static values only and are reserved for the standalone manual toggle
|
|
427
|
+
- Architecture: hosts control theme via inline vars (cascade), standalone controls via `data-theme` (static) — no conflict
|
|
428
|
+
|
|
429
|
+
## [0.2.20] — 2026-04-29
|
|
430
|
+
|
|
431
|
+
### Theme Toggle Fix
|
|
432
|
+
- **Fixed**: dark/light toggle icon flipped but colours didn't change — `@media (prefers-color-scheme: dark) :root:not(...)` at specificity (0,2,0) beat `[data-theme]` at (0,1,0). Bumped to `:root[data-theme="dark/light"]` (0,2,0) so toggle wins by source order
|
|
433
|
+
|
|
434
|
+
### VS Code Theme Sync
|
|
435
|
+
- `syncVsCodeTheme()` — reads `data-vscode-theme-kind` from `document.body`, maps to `data-theme` on `:root` (`vscode-dark` / `vscode-high-contrast` → `dark`, else `light`)
|
|
436
|
+
- `MutationObserver` watches for VS Code theme switches and keeps `data-theme` in sync automatically
|
|
437
|
+
- Only active in standalone / VS Code context (skipped when MCP Apps bridge is present)
|
|
438
|
+
|
|
439
|
+
### Tests
|
|
440
|
+
- Added `renderer-destroy.test.ts` — 12 tests for component destroy hook lifecycle
|
|
441
|
+
- **1142 tests** passing across 32 files
|
|
442
|
+
|
|
443
|
+
## [0.2.19] — 2026-04-29
|
|
444
|
+
|
|
445
|
+
### SDK Type Compatibility
|
|
446
|
+
- `McpToolResult` now structurally assignable to `@modelcontextprotocol/sdk` `CallToolResult` — no cast needed when returning `display()` from SDK tool handlers
|
|
447
|
+
- Added `[key: string]: unknown` index signature (satisfies SDK's `Result` base)
|
|
448
|
+
- Split `McpResourceContent.resource` into discriminated union (`McpTextResourceContents | McpBlobResourceContents`) matching SDK's `EmbeddedResource`
|
|
449
|
+
- Added optional `annotations?` and `_meta?` on all content types
|
|
450
|
+
|
|
451
|
+
### Host Theme Token Mapping
|
|
452
|
+
- **CSS fallback chain**: all design tokens in `prefab.css` resolve through 3 tiers — MCP Apps spec vars (`--color-background-primary`) → VS Code vars (`--vscode-editor-background`) → static defaults
|
|
453
|
+
- Applies to all theme blocks: `:root`, `@media (prefers-color-scheme: dark)`, `[data-theme="dark"]`, `[data-theme="light"]`
|
|
454
|
+
- Downstream MCP servers no longer need custom CSS for Claude Desktop or VS Code webview theming
|
|
455
|
+
- Added `--shadow-sm/md/lg` and `--border-radius-*` → `--radius` mapping from MCP Apps spec
|
|
456
|
+
- `applyHostTheme()` now injects host-provided `@font-face` / `@import` CSS from `styles.css.fonts` (idempotent `<style>` tag)
|
|
457
|
+
- `HostTheme.fontCss` field added; extracted from both `ui/initialize` and `ui/notifications/host-context-changed`
|
|
458
|
+
|
|
459
|
+
## [0.2.18] — 2026-04-29
|
|
460
|
+
|
|
461
|
+
### Builder API Improvements
|
|
462
|
+
- `display_success(title, body?)` — success-variant alert helper
|
|
463
|
+
- `resourceMeta(opts)` — build `_meta` for `resources/read` (CSP, permissions, domain, border)
|
|
464
|
+
- `PREFAB_CDN_META` — pre-built meta with jsDelivr CDN CSP for common deployments
|
|
465
|
+
- `structuredContent` on all display helpers (`display`, `display_form`, `display_update`, `display_error`, `display_success`)
|
|
466
|
+
- `PrefabApp.toMcpResult()` — returns `{ content, structuredContent }` for direct SDK tool handler return
|
|
467
|
+
- MCP actions: `RequestDisplayMode` action + `displayMode` option on `CallTool`
|
|
468
|
+
- New component support: `Embed`, `Markdown`, `Mermaid`, `CodeBlock` builder classes
|
|
469
|
+
|
|
470
|
+
### Tests
|
|
471
|
+
- **1130 tests** passing across 31 files
|
|
472
|
+
|
|
473
|
+
## [0.2.17] — 2026-04-28
|
|
474
|
+
|
|
475
|
+
### Auto-Resize
|
|
476
|
+
- `Bridge.setupAutoResize(el)` — `ResizeObserver` on the target element, notifies the host whenever the content dimensions change via `ui/notifications/size-changed` (JSON-RPC) or `prefab:size-changed` (prefab protocol). Mirrors the ext-apps SDK `autoResize: true` behaviour without the SDK dependency.
|
|
477
|
+
- `sendRpcNotification()` — fire-and-forget JSON-RPC notification (no `id`, no response expected)
|
|
478
|
+
- `PrefabApp.setupAutoResize(target)` — public API accepting selector or element
|
|
479
|
+
- `renderer.auto.min.js` now auto-observes `#root` after boot — hosts get size updates out of the box
|
|
480
|
+
- Deduplicates identical dimensions, fires initial notification immediately
|
|
481
|
+
- 4 new tests — **1130 total tests**
|
|
482
|
+
|
|
483
|
+
## [0.2.16] — 2026-04-28
|
|
484
|
+
|
|
485
|
+
### Docs
|
|
486
|
+
- Added `appInfo` vs `clientInfo` Common Pitfall section to `mcp-apps.md`
|
|
487
|
+
- Added ext-apps SDK vs native `ui/*` JSON-RPC comparison table
|
|
488
|
+
- Added ext-apps SDK source references to Reference section
|
|
489
|
+
|
|
490
|
+
## [0.2.15] — 2026-04-28
|
|
491
|
+
|
|
492
|
+
### Bug Fix: Claude Desktop / ChatGPT Breakage
|
|
493
|
+
- **Fixed**: `ui/initialize` handshake sent `clientInfo` instead of `appInfo` — hosts validate with Zod schema that requires `appInfo`, causing silent handshake failure (blank iframe, no error). The ext-apps SDK fallback in v0.2.11 masked this; removing the SDK in v0.2.12 exposed it.
|
|
494
|
+
- `Bridge.initialize()` error handling: `Promise.any` wrapped in try/catch, rethrows as descriptive `Error('Bridge init failed — no host responded')` with `{ cause }` preserving the `AggregateError`
|
|
495
|
+
- 2 new tests: `appInfo` field validation, clear error on dual-protocol failure — **1126 total tests**
|
|
496
|
+
|
|
497
|
+
## [0.2.14] — 2026-04-28
|
|
498
|
+
|
|
499
|
+
### Built-in Theme Toggle
|
|
500
|
+
- `createThemeToggle(root, options?)` — renders a floating sun/moon toggle button with two-way sync to `data-theme` attribute via `MutationObserver`
|
|
501
|
+
- `PrefabRenderer.mount()` auto-attaches toggle by default (opt out with `themeToggle: false`)
|
|
502
|
+
- Toggle preserved across re-renders
|
|
503
|
+
|
|
504
|
+
### Bug Fixes
|
|
505
|
+
- `appendState` action: support `item` alias (Python SDK compat)
|
|
506
|
+
- Charts: restore CSS custom properties in SVG presentation attributes for dark mode
|
|
507
|
+
|
|
508
|
+
## [0.2.13] — 2026-04-27
|
|
509
|
+
|
|
510
|
+
### Bug Fixes
|
|
511
|
+
- Renderer: remove all inline `theme-variable` styles, rely on CSS classes (CSP compliance)
|
|
512
|
+
- Badge: use `Partial<Record>` to satisfy `strict-boolean-expressions` lint rule
|
|
513
|
+
- Demo: fix theme toggle, favicon 404, copy-MCP-button styling
|
|
514
|
+
- Docs: inject CDN version from `package.json` at build time
|
|
515
|
+
|
|
516
|
+
## [0.2.12] — 2026-04-27
|
|
517
|
+
|
|
518
|
+
### Bundle Size Reduction
|
|
519
|
+
- **Removed** `@modelcontextprotocol/ext-apps` SDK dependency — 405 KB → 80 KB bundle
|
|
520
|
+
- Bridge now speaks native `ui/*` JSON-RPC without the SDK wrapper
|
|
521
|
+
- Docs updated to remove ext-apps references
|
|
522
|
+
- ⚠️ **Regression**: `clientInfo` field name broke Claude Desktop / ChatGPT (fixed in v0.2.15)
|
|
523
|
+
|
|
524
|
+
## [0.2.11] — 2026-04-27
|
|
525
|
+
|
|
526
|
+
### Theming
|
|
527
|
+
- `data-theme` attribute support in `prefab.css` — light/dark mode via attribute selector
|
|
528
|
+
- MCP agent skill: attach `prefab-skill.zip` to GitHub releases
|
|
529
|
+
|
|
530
|
+
## [0.2.10] — 2026-04-27
|
|
531
|
+
|
|
532
|
+
### Docs & Polish
|
|
533
|
+
- Select options shorthand syntax
|
|
534
|
+
- Remote usage mode documentation
|
|
535
|
+
- Brand assets, favicon, logo paths
|
|
536
|
+
- Playground: dark preview background
|
|
537
|
+
- Lint fixes: `Array<T>` → `T[]`, control char regex, `RegExp.exec`
|
|
538
|
+
- Markdown renderer: protect inline code from formatting, fix CRLF infinite loop
|
|
539
|
+
- DRY: `serializeCallbacks`, camelCase display aliases
|
|
540
|
+
|
|
541
|
+
## [0.2.9] — 2026-04-26
|
|
542
|
+
|
|
543
|
+
### Bug Fix
|
|
544
|
+
- `autoTable` column keys now match serialized row keys
|
|
545
|
+
|
|
546
|
+
## [0.2.8] — 2026-04-26
|
|
547
|
+
|
|
548
|
+
### Universal MCP Apps Bridge
|
|
549
|
+
- **Fixed**: `renderer.auto.min.js` now works in VS Code, Claude Desktop, ChatGPT, and all MCP Apps hosts without any inline adapter code
|
|
550
|
+
- `Bridge.initialize()` races `prefab:init` and `ui/initialize` JSON-RPC **in parallel** — whichever host protocol responds first wins. Eliminates the 1.5s dead time on JSON-RPC hosts
|
|
551
|
+
- `app()` now buffers `tool-result` events — host can send results before `onToolResult` is registered without data loss
|
|
552
|
+
- `auto.ts` defers `boot()` to `DOMContentLoaded` (or microtask if already loaded)
|
|
553
|
+
|
|
554
|
+
## [0.2.7] — 2026-04-25
|
|
555
|
+
|
|
556
|
+
### Chart Formatting
|
|
557
|
+
- Generic pipe formatting for chart axes and tooltips
|
|
558
|
+
- `tooltipXKey` — separate data key for tooltip vs x-axis labels
|
|
559
|
+
- Fix: remove non-null assertion in PieChart tooltip key lookup (lint)
|
|
560
|
+
|
|
561
|
+
## [0.2.6] — 2026-04-25
|
|
562
|
+
|
|
563
|
+
### Chart Tooltips
|
|
564
|
+
- Production-quality tooltips with crosshair, data dots, null gaps, a11y, touch support
|
|
565
|
+
|
|
566
|
+
## [0.2.5] — 2026-04-25
|
|
567
|
+
|
|
568
|
+
### Custom Renderers
|
|
569
|
+
- `registerComponent(type, renderFn)` exposed on `window.prefab` for custom component renderers
|
|
570
|
+
|
|
571
|
+
## [0.2.4] — 2026-04-25
|
|
572
|
+
|
|
573
|
+
### Chart Axes
|
|
574
|
+
- Y-axis, X-axis labels, grid lines, dual Y-axis support
|
|
575
|
+
|
|
576
|
+
## [0.2.3] — 2026-04-25
|
|
577
|
+
|
|
578
|
+
### JSON-RPC Protocol
|
|
579
|
+
- Native `ui/*` JSON-RPC protocol — zero-adapter VS Code support
|
|
580
|
+
|
|
581
|
+
## [0.2.2] — 2026-04-25
|
|
582
|
+
|
|
583
|
+
### Auto-Mount Bundle
|
|
584
|
+
- `renderer.auto.min.js` — CSP-safe self-executing bundle
|
|
585
|
+
- CI: attach `renderer.auto.min.js` + `prefab.css` to GitHub releases
|
|
586
|
+
- Wire compat: `callTool` action alias + `Condition` component
|
|
587
|
+
|
|
588
|
+
### Docs
|
|
589
|
+
- Signal, Collection, Ref, sugar actions, find/dot pipes, Detail/MasterDetail, CSS theme, versioned CDN
|
|
590
|
+
- Live playground (Monaco editor + shareable URLs + AI prompt)
|
|
591
|
+
|
|
592
|
+
## [0.2.1] — 2026-04-25
|
|
593
|
+
|
|
594
|
+
### Bug Fix: If/Elif/Else Conditional Chains
|
|
595
|
+
- **Fixed**: `Elif` and `Else` nodes rendered independently instead of being consumed by the preceding `If` chain
|
|
596
|
+
- New `renderChildArray()` detects `If/Elif/Else` sibling sequences as a single conditional chain
|
|
597
|
+
- 55 new tests — **913 total tests**
|
|
598
|
+
|
|
599
|
+
### Bug Fix: Browser Pipe Registration
|
|
600
|
+
- **Fixed**: Custom pipes registered in Node were not available in the browser renderer bundle
|
|
601
|
+
- `PrefabApp({ pipes })` accepts pipe functions, serializes source into wire format
|
|
602
|
+
- Renderer `mount()` hydrates wire pipes via `new Function()` before first render
|
|
603
|
+
- Built-in pipes cannot be shadowed by wire pipes (security)
|
|
604
|
+
- 12 new tests — **858 total tests**
|
|
605
|
+
|
|
606
|
+
## [0.2.0] — 2026-04-25
|
|
607
|
+
|
|
608
|
+
### Breaking: Wire Format v0.2
|
|
609
|
+
- `$prefab.version` bumped to `0.2`
|
|
610
|
+
- Initial release of the `0.2.x` series with all v0.1.x features plus the universal MCP Apps bridge
|
|
611
|
+
|
|
612
|
+
## [0.1.10] — 2026-04-24
|
|
613
|
+
|
|
614
|
+
### Action-Builder Sugar
|
|
615
|
+
- `set(signal, value)` — ergonomic wrapper for `new SetState(signal.key, value)`
|
|
616
|
+
- `toggle(signal)` — wrapper for `new ToggleState(signal.key)`
|
|
617
|
+
- `append(collection, item, index?)` — wrapper for `new AppendState(collection.stateKey, item)`
|
|
618
|
+
- `pop(collection, indexOrValue?)` — wrapper for `new PopState(collection.stateKey, indexOrValue)`, defaults to last element
|
|
619
|
+
- All helpers accept `Signal`, `Collection`, or raw `string` key via `StateTarget` type
|
|
620
|
+
- `set()` passes through `SetStateOpts` (onSuccess/onError callbacks)
|
|
621
|
+
- 17 new tests in `test/sugar.test.ts` — **846 total tests**
|
|
622
|
+
|
|
623
|
+
## [0.1.8] — 2026-04-24
|
|
624
|
+
|
|
625
|
+
### Reactive Primitives
|
|
626
|
+
- `signal(key, initial)` — named reactive scalar for wire format, auto-registers state
|
|
627
|
+
- `collection(key, rows, { key })` — named keyed array, auto-registers state
|
|
628
|
+
- `Ref<T>` — lazy pipe expression referencing a row in a collection via `collection.by(signal)`
|
|
629
|
+
- Typed `Ref.dot(field)` — returns `Ref<T[K]>` with autocomplete on `keyof T`
|
|
630
|
+
- `Ref.formatted(field, pipe, ...args)` — sugar for `.dot(field).pipe(pipe)`
|
|
631
|
+
- `Rx.pipe(name, ...args)` — public variadic pipe builder (was private single-arg)
|
|
632
|
+
- `Ref.pipe(name, ...args)` — delegates to `Rx.pipe()`
|
|
633
|
+
|
|
634
|
+
### Pipe Extension Point
|
|
635
|
+
- `registerPipe(name, fn)` — global custom pipe registry for companion packages
|
|
636
|
+
- `unregisterPipe(name)` — remove a pipe (tests)
|
|
637
|
+
- `listPipes()` — list registered names (debugging)
|
|
638
|
+
- Built-in pipes always shadow custom pipes (safety)
|
|
639
|
+
- Re-registration warns and overwrites (HMR-friendly)
|
|
640
|
+
- Custom pipes receive variadic parsed args (`| date:'long'`, `| between:1,10`)
|
|
641
|
+
|
|
642
|
+
### Selection & Master-Detail
|
|
643
|
+
- `DataTable({ from, selected })` — auto-wires `rowKey`, `onRowClick → SetState`, highlight
|
|
644
|
+
- `Detail({ of, empty, children })` — conditional pane, shows children when ref resolves
|
|
645
|
+
- `MasterDetail({ masterWidth, gap, children })` — two-pane flex layout
|
|
646
|
+
- `col()` descriptor overload: `col({ key, header, format, accessor, sortable })`
|
|
647
|
+
- `format` on columns applies pipe (built-in or custom) to cell values
|
|
648
|
+
- `accessor` on columns resolves pipe expressions per cell (`name | humanName`)
|
|
649
|
+
|
|
650
|
+
### Auto State Collection
|
|
651
|
+
- `signal()` and `collection()` factories auto-register into a global collector
|
|
652
|
+
- `PrefabApp` constructor drains collector — no more `state: { ...c.toState(), ...s.toState() }`
|
|
653
|
+
- Explicit `state` overrides auto-collected on key conflicts
|
|
654
|
+
- Duplicate state keys warn (`[prefab] state key "X" registered multiple times`)
|
|
655
|
+
- `resetAutoState()` exported for test cleanup
|
|
656
|
+
|
|
657
|
+
### Renderer
|
|
658
|
+
- `find` pipe filter — O(1) keyed lookup with generation-aware cache
|
|
659
|
+
- `dot` pipe filter — extract property from object
|
|
660
|
+
- `Store.generation` counter — monotonically increasing, invalidates find cache on mutation
|
|
661
|
+
- `applyFilter` falls through to custom pipe registry after built-ins
|
|
662
|
+
- `RxStr` widened to `string | Rx | Ref` — Ref works in all component props
|
|
663
|
+
|
|
664
|
+
### Bug Fixes (TDD)
|
|
665
|
+
- `find` pipe: numeric key coercion (`'2' !== 2`) — fixed with `String()` on both sides
|
|
666
|
+
- `find` pipe: scope dot-path resolution (`$item.managerId`) — walk scope object
|
|
667
|
+
- `find` cache: stale after in-place mutation — fixed with generation counter
|
|
668
|
+
- `Detail`: `0` treated as truthy — added explicit `!== 0` check
|
|
669
|
+
- `col({ format })`: built-in pipes silently ignored — route through `applyFilter`
|
|
670
|
+
- `col({ accessor, format })`: double-applied formatting — skip format when accessor present
|
|
671
|
+
- Duplicate auto-state keys: silent overwrite — now warns
|
|
672
|
+
|
|
673
|
+
### Tests
|
|
674
|
+
- 829 tests passing across 24 files
|
|
675
|
+
- New: `test/signal-collection.test.ts` (67 tests)
|
|
676
|
+
- New: `test/pipes.test.ts` (17 tests)
|
|
677
|
+
- New: `test/tdd-bugs.test.ts` (6 tests)
|
|
678
|
+
|
|
679
|
+
## [0.1.0] — 2026-04-20
|
|
680
|
+
|
|
681
|
+
Initial release.
|
|
682
|
+
|
|
683
|
+
### Component Library
|
|
684
|
+
- Core classes: `Component`, `ContainerComponent`, `StatefulComponent`
|
|
685
|
+
- `PrefabApp` wrapper with `$prefab` v0.2 wire format and `toHTML()` self-contained page export
|
|
686
|
+
- `rx()` reactive expression builder (pipes, comparisons, ternary, built-in vars)
|
|
687
|
+
- 80+ components across layout, typography, card, data, form, chart, control, interactive, media, alert
|
|
688
|
+
- Table components: `Table`, `TableHead`, `TableBody`, `TableFooter`, `TableRow`, `TableHeader`, `TableCell`, `TableCaption`, `ExpandableRow`
|
|
689
|
+
- Form extensions: `Radio`, `RadioGroup`, `Combobox`, `ComboboxOption`, `Calendar`, `DatePicker`, `Field`, `ChoiceCard`, and more
|
|
690
|
+
- Chart extensions: `RadialChart`, `Histogram` (in addition to Bar, Line, Area, Pie, Radar, Scatter, Sparkline)
|
|
691
|
+
- Composition: `Define`, `Use`, `Slot` for template reuse
|
|
692
|
+
- Client actions: `SetState`, `ToggleState`, `AppendState`, `PopState`, `ShowToast`, `CloseOverlay`, `OpenLink`, `SetInterval`, `Fetch`, `OpenFilePicker`, `CallHandler`
|
|
693
|
+
- MCP actions: `CallTool`, `SendMessage`, `UpdateContext`, `RequestDisplayMode`
|
|
694
|
+
|
|
695
|
+
### MCP Display Helpers
|
|
696
|
+
- `display()`, `display_form()`, `display_update()`, `display_error()`
|
|
697
|
+
|
|
698
|
+
### Auto-Renderers
|
|
699
|
+
- `autoDetail`, `autoTable`, `autoChart`, `autoForm`, `autoComparison`, `autoMetrics`, `autoTimeline`, `autoProgress`
|
|
700
|
+
|
|
701
|
+
### Browser Renderer
|
|
702
|
+
- Vanilla DOM renderer — 55+ components, zero framework dependencies
|
|
703
|
+
- Reactive `Store` with get/set/merge/toggle/append
|
|
704
|
+
- Rx expression engine: ternary, logical, arithmetic, 15+ pipes, dot access, scoped variables
|
|
705
|
+
- Action dispatcher — 15 action types
|
|
706
|
+
- MCP transport — HTTP POST to `/mcp/tools/call`
|
|
707
|
+
- Theme engine — CSS custom properties from `theme` field (light/dark)
|
|
708
|
+
- Chart renderer — built-in SVG for Bar, Line, Area, Pie
|
|
709
|
+
- Mermaid integration — delegates to global `mermaid` if available
|
|
710
|
+
- IIFE bundle: `renderer.min.js` (54KB) for `<script>` tag usage, `window.prefab` global
|
|
711
|
+
|
|
712
|
+
### ext-apps Bridge
|
|
713
|
+
- `app()` one-call factory with PostMessage transport, host theme mapping, lifecycle hooks
|
|
714
|
+
- Capability negotiation, display mode requests, tool input/result/cancelled/partial events
|
|
715
|
+
- Auto-detect environment: iframe → PostMessage, standalone → HTTP transport
|
|
716
|
+
|
|
717
|
+
### Validation & Accessibility
|
|
718
|
+
- `validateWireFormat()` + `isValidWireFormat()` with detailed error reporting
|
|
719
|
+
- Stylesheet injection — renderer applies `stylesheets` field as `<style>` tags
|
|
720
|
+
- ARIA roles/attributes and keyboard navigation on all interactive components
|
|
721
|
+
|
|
722
|
+
### Infra
|
|
723
|
+
- GitHub Actions CI (test + build on push) and publish (npm on tag)
|
|
724
|
+
- 253 tests passing across 11 files (570 assertions)
|
|
725
|
+
- MIT license
|