@gmb/bitmark-parser-generator 5.22.0 → 5.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  BitmarkStringGenerator: () => BitmarkStringGenerator,
32
32
  BitmarkVersion: () => BitmarkVersion,
33
33
  BodyTextFormat: () => BodyTextFormat,
34
+ BoundingBoxIndex: () => BoundingBoxIndex,
34
35
  Builder: () => Builder,
35
36
  CardSetVersion: () => CardSetVersion,
36
37
  InfoFormat: () => InfoFormat,
@@ -326,6 +327,7 @@ var BitType2 = {
326
327
  footNote: "foot-note",
327
328
  formFreeText: "form-free-text",
328
329
  formula: "formula",
330
+ formulaImage: "formula-image",
329
331
  gapText: "gap-text",
330
332
  gapTextInstructionGrouped: "gap-text-instruction-grouped",
331
333
  glossaryTerm: "glossary-term",
@@ -750,8 +752,10 @@ var TagFormat = {
750
752
  // If the value is treated as a boolean
751
753
  invertedBoolean: "invertedBoolean",
752
754
  // If the value is treated as a boolean with the value inverted (e.g. isLongAnswer ==> isShortAnswer = false)
753
- enumeration: "enumeration"
755
+ enumeration: "enumeration",
754
756
  // If the value is treated as an enumeration (the value is a string that must be one of a predefined set of values)
757
+ coordinates: "coordinates"
758
+ // Comma-separated list of 4 numbers (e.g. bounding-box x, y, x1, y1)
755
759
  };
756
760
 
757
761
  // src/model/enum/TextFormat.ts
@@ -774,10 +778,12 @@ var TextFormat2 = {
774
778
  var import_superenum7 = require("@ncoderz/superenum");
775
779
  var CardSetConfigKey = {
776
780
  flashcard: "flashcard",
781
+ flashcard1: "flashcard1",
777
782
  definitionList: "definitionList",
778
783
  elements: "elements",
779
784
  statements: "statements",
780
785
  quiz: "quiz",
786
+ quizResponses: "quizResponses",
781
787
  feedback: "feedback",
782
788
  questions: "questions",
783
789
  matchPairs: "matchPairs",
@@ -789,6 +795,7 @@ var CardSetConfigKey = {
789
795
  pronunciationTable: "pronunciationTable",
790
796
  botActionResponses: "botActionResponses",
791
797
  exampleBitList: "exampleBitList",
798
+ pageFooterSections: "pageFooterSections",
792
799
  clozeList: "clozeList",
793
800
  ingredients: "ingredients",
794
801
  // DEPRECATED - TO BE REMOVED IN THE FUTURE
@@ -836,7 +843,11 @@ var AbstractTagConfig = class {
836
843
  // Default: 0
837
844
  __publicField(this, "chain");
838
845
  __publicField(this, "jsonKey");
839
- // If the json key is different from the tag
846
+ // Legacy string-form jsonKey
847
+ __publicField(this, "exportJsonKey");
848
+ // New JSON-pattern jsonKey (export-only)
849
+ __publicField(this, "hasExportJsonKey");
850
+ // True iff exportJsonKey was explicitly set
840
851
  __publicField(this, "deprecated");
841
852
  this.type = params.type;
842
853
  this.configKey = params.configKey;
@@ -845,6 +856,8 @@ var AbstractTagConfig = class {
845
856
  this.minCount = params.minCount;
846
857
  this.chain = params.chain;
847
858
  this.jsonKey = params.jsonKey;
859
+ this.exportJsonKey = params.exportJsonKey;
860
+ this.hasExportJsonKey = params.hasExportJsonKey;
848
861
  this.deprecated = params.deprecated;
849
862
  }
850
863
  };
@@ -854,7 +867,6 @@ var MarkupTagConfig = class extends AbstractTagConfig {
854
867
  constructor(params) {
855
868
  super({
856
869
  type: BitTagConfigKeyType.tag,
857
- jsonKey: void 0,
858
870
  ...params
859
871
  });
860
872
  __publicField(this, "type", BitTagConfigKeyType.tag);
@@ -966,8 +978,8 @@ var ResourceTagConfig = class extends AbstractTagConfig {
966
978
 
967
979
  // src/model/config/CardVariantConfig.ts
968
980
  var CardVariantConfig = class {
969
- // JSON path for body text
970
- constructor(tags2, bodyAllowed, bodyRequired, repeatCount, jsonKey) {
981
+ // Body text format. Default: bitmark++
982
+ constructor(tags2, bodyAllowed, bodyRequired, repeatCount, jsonKey, exportJsonKey, hasExportJsonKey = false, format) {
971
983
  __publicField(this, "tags");
972
984
  __publicField(this, "bodyAllowed");
973
985
  // Default: true
@@ -977,11 +989,19 @@ var CardVariantConfig = class {
977
989
  // Default: 1
978
990
  // JSON mapping fields
979
991
  __publicField(this, "jsonKey");
992
+ // JSON path for body text (legacy)
993
+ __publicField(this, "exportJsonKey");
994
+ // New JSON-pattern jsonKey
995
+ __publicField(this, "hasExportJsonKey");
996
+ __publicField(this, "format");
980
997
  this.tags = tags2;
981
998
  this.bodyAllowed = bodyAllowed == null ? true : bodyAllowed;
982
999
  this.bodyRequired = bodyRequired;
983
1000
  this.repeatCount = repeatCount;
984
1001
  this.jsonKey = jsonKey;
1002
+ this.exportJsonKey = exportJsonKey;
1003
+ this.hasExportJsonKey = hasExportJsonKey;
1004
+ this.format = format;
985
1005
  }
986
1006
  toString(options) {
987
1007
  const opts = Object.assign({}, options);
@@ -1005,14 +1025,18 @@ ${tag.toString(opts)}`;
1005
1025
 
1006
1026
  // src/model/config/CardSideConfig.ts
1007
1027
  var CardSideConfig = class {
1008
- constructor(name, repeat, variants, jsonKey) {
1028
+ constructor(name, repeat, variants, jsonKey, exportJsonKey, hasExportJsonKey = false) {
1009
1029
  __publicField(this, "name");
1010
1030
  __publicField(this, "repeat");
1011
1031
  __publicField(this, "jsonKey");
1032
+ __publicField(this, "exportJsonKey");
1033
+ __publicField(this, "hasExportJsonKey");
1012
1034
  __publicField(this, "variants");
1013
1035
  this.name = name;
1014
1036
  this.repeat = repeat;
1015
1037
  this.jsonKey = jsonKey;
1038
+ this.exportJsonKey = exportJsonKey;
1039
+ this.hasExportJsonKey = hasExportJsonKey;
1016
1040
  this.variants = variants;
1017
1041
  }
1018
1042
  toString(options) {
@@ -1034,13 +1058,17 @@ var CardSideConfig = class {
1034
1058
 
1035
1059
  // src/model/config/CardSetConfig.ts
1036
1060
  var CardSetConfig = class {
1037
- constructor(configKey, jsonKey, sections, sides) {
1061
+ constructor(configKey, jsonKey, exportJsonKey, hasExportJsonKey, sections, sides) {
1038
1062
  __publicField(this, "configKey");
1039
1063
  __publicField(this, "jsonKey");
1064
+ __publicField(this, "exportJsonKey");
1065
+ __publicField(this, "hasExportJsonKey");
1040
1066
  __publicField(this, "sections");
1041
1067
  __publicField(this, "sides");
1042
1068
  this.configKey = configKey;
1043
1069
  this.jsonKey = jsonKey;
1070
+ this.exportJsonKey = exportJsonKey;
1071
+ this.hasExportJsonKey = hasExportJsonKey;
1044
1072
  this.sections = sections;
1045
1073
  this.sides = sides;
1046
1074
  }
@@ -1184,12 +1212,17 @@ var groupKeys = {
1184
1212
  group_standardAllBits: "group_standardAllBits",
1185
1213
  group_standardItemLead: "group_standardItemLead",
1186
1214
  group_standardItemLeadInstructionHint: "group_standardItemLeadInstructionHint",
1215
+ group_standardInstructionHint: "group_standardInstructionHint",
1187
1216
  group_standardTags: "group_standardTags",
1217
+ group_standardTagsNoItemLead: "group_standardTagsNoItemLead",
1188
1218
  group_imageSource: "group_imageSource",
1189
1219
  group_technicalTerm: "group_technicalTerm",
1190
1220
  group_person: "group_person",
1191
1221
  group_gap: "group_gap",
1192
1222
  group_trueFalse: "group_trueFalse",
1223
+ group_trueFalseInlineHighlight: "group_trueFalseInlineHighlight",
1224
+ group_trueFalseInlineSelect: "group_trueFalseInlineSelect",
1225
+ group_trueFalseResponses: "group_trueFalseResponses",
1193
1226
  group_markConfig: "group_markConfig",
1194
1227
  group_mark: "group_mark",
1195
1228
  group_bookCommon: "group_bookCommon",
@@ -1197,14 +1230,18 @@ var groupKeys = {
1197
1230
  group_advertisingCommon: "group_advertisingCommon",
1198
1231
  group_platformStylesCommon: "group_platformStylesCommon",
1199
1232
  group_quizCommon: "group_quizCommon",
1233
+ group_backgroundWallpaper: "group_backgroundWallpaper",
1234
+ group_imageNoZoom: "group_imageNoZoom",
1200
1235
  group_resourceBitTags: "group_resourceBitTags",
1201
1236
  group_resourceCommon: "group_resourceCommon",
1202
1237
  group_resourceImageCommon: "group_resourceImageCommon",
1238
+ group_resourceImageCommonNoZoom: "group_resourceImageCommonNoZoom",
1203
1239
  group_resourceAudioCommon: "group_resourceAudioCommon",
1204
1240
  group_resourceVideoCommon: "group_resourceVideoCommon",
1205
1241
  group_resourceIcon: "group_resourceIcon",
1206
1242
  // Alias for image
1207
1243
  group_resourceImage: "group_resourceImage",
1244
+ group_resourceImageNoZoom: "group_resourceImageNoZoom",
1208
1245
  group_resourceImageEmbed: "group_resourceImageEmbed",
1209
1246
  group_resourceImageLink: "group_resourceImageLink",
1210
1247
  group_resourceImageResponsive: "group_resourceImageResponsive",
@@ -1335,6 +1372,7 @@ var propertyKeys = {
1335
1372
  property_formula: "@formula",
1336
1373
  property_fullName: "@fullName",
1337
1374
  property_groupTag: "@groupTag",
1375
+ property_gUri: "@gUri",
1338
1376
  property_handInAcceptFileType: "@handInAcceptFileType",
1339
1377
  property_handInInstruction: "@handInInstruction",
1340
1378
  property_handInRequirement: "@handInRequirement",
@@ -1470,6 +1508,7 @@ var propertyKeys = {
1470
1508
  property_siteName: "@siteName",
1471
1509
  property_size: "@size",
1472
1510
  property_slug: "@slug",
1511
+ property_sourceBB: "@sourceBB",
1473
1512
  property_sourceDocument: "@sourceDocument",
1474
1513
  property_sourceRL: "@sourceRL",
1475
1514
  property_spaceId: "@spaceId",
@@ -1479,6 +1518,7 @@ var propertyKeys = {
1479
1518
  property_src4x: "@src4x",
1480
1519
  property_srcAlt: "@srcAlt",
1481
1520
  property_stripePricingTableId: "@stripePricingTableId",
1521
+ property_stripePricingTableDescriptiveName: "@stripePricingTableDescriptiveName",
1482
1522
  property_stripePublishableKey: "@stripePublishableKey",
1483
1523
  property_subject: "@subject",
1484
1524
  property_subtype: "@subtype",
@@ -2223,25 +2263,159 @@ var CARDSETS = {
2223
2263
  //
2224
2264
  [CardSetConfigKey.flashcard]: {
2225
2265
  jsonKey: "cards",
2266
+ exportJsonKey: { cards: "$" },
2267
+ sides: [
2268
+ {
2269
+ name: "question",
2270
+ variants: [
2271
+ {
2272
+ jsonKey: "question.text",
2273
+ exportJsonKey: [{ "@absent": { question: {} } }, { question: { text: "$" } }],
2274
+ tags: [
2275
+ {
2276
+ key: ConfigKey.group_standardTags,
2277
+ description: "Standard tags for the flashcard."
2278
+ },
2279
+ {
2280
+ exportJsonKey: { title: "$" },
2281
+ key: ConfigKey.tag_title,
2282
+ description: "Title of the flashcard."
2283
+ },
2284
+ {
2285
+ key: ConfigKey.group_resourceIcon,
2286
+ description: "Icon resource for the flashcard.",
2287
+ jsonKey: "question.icon|resource(type=image, key=image)",
2288
+ exportJsonKey: { question: { icon: { type: "image", image: { src: "$" } } } }
2289
+ },
2290
+ {
2291
+ key: ConfigKey.property_example,
2292
+ exportJsonKey: [
2293
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2294
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2295
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2296
+ ],
2297
+ description: "Example marker for the flashcard question.",
2298
+ format: TagFormat.bitmarkText,
2299
+ nullable: true
2300
+ }
2301
+ ]
2302
+ }
2303
+ ]
2304
+ },
2305
+ {
2306
+ name: "answer",
2307
+ variants: [
2308
+ {
2309
+ jsonKey: "answer.text",
2310
+ exportJsonKey: [{ "@absent": { answer: {} } }, { answer: { text: "$" } }],
2311
+ tags: [
2312
+ {
2313
+ key: ConfigKey.group_standardTags,
2314
+ description: "Standard tags for the flashcard."
2315
+ },
2316
+ {
2317
+ exportJsonKey: { title: "$" },
2318
+ key: ConfigKey.tag_title,
2319
+ description: "Title of the flashcard."
2320
+ },
2321
+ {
2322
+ key: ConfigKey.group_resourceIcon,
2323
+ description: "Icon resource for the flashcard.",
2324
+ jsonKey: "answer.icon|resource(type=image, key=image)",
2325
+ exportJsonKey: { answer: { icon: { type: "image", image: { src: "$" } } } }
2326
+ },
2327
+ {
2328
+ key: ConfigKey.property_example,
2329
+ exportJsonKey: [
2330
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2331
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2332
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2333
+ ],
2334
+ description: "Example marker for the flashcard answer.",
2335
+ format: TagFormat.bitmarkText,
2336
+ nullable: true
2337
+ }
2338
+ ]
2339
+ },
2340
+ {
2341
+ jsonKey: "alternativeAnswers[].text",
2342
+ exportJsonKey: { alternativeAnswers: [{ text: "$" }] },
2343
+ tags: [
2344
+ {
2345
+ key: ConfigKey.group_standardTags,
2346
+ description: "Standard tags for the flashcard."
2347
+ },
2348
+ {
2349
+ exportJsonKey: { title: "$" },
2350
+ key: ConfigKey.tag_title,
2351
+ description: "Title of the flashcard."
2352
+ },
2353
+ {
2354
+ key: ConfigKey.group_resourceIcon,
2355
+ description: "Icon resource for the flashcard.",
2356
+ jsonKey: "alternativeAnswers[].icon|resource(type=image, key=image)",
2357
+ exportJsonKey: {
2358
+ alternativeAnswers: [{ icon: { type: "image", image: { src: "$" } } }]
2359
+ }
2360
+ }
2361
+ ],
2362
+ repeatCount: Count.infinity
2363
+ }
2364
+ ]
2365
+ }
2366
+ ]
2367
+ },
2368
+ //
2369
+ // flashcard1 — single-card variant of flashcard. Same shape, but capped
2370
+ // at one card via `sections.default.maxCount = 1` (PLAN-085). Used by
2371
+ // the `flashcard-1` bit type; the multi-card `flashcard` and
2372
+ // `q-and-a-card` bit types continue to use the unbounded `flashcard`
2373
+ // cardset.
2374
+ //
2375
+ [CardSetConfigKey.flashcard1]: {
2376
+ jsonKey: "cards",
2377
+ exportJsonKey: { cards: "$" },
2378
+ sections: {
2379
+ default: {
2380
+ jsonKey: "cards",
2381
+ exportJsonKey: { cards: "$" },
2382
+ isDefault: true,
2383
+ maxCount: 1
2384
+ }
2385
+ },
2226
2386
  sides: [
2227
2387
  {
2228
2388
  name: "question",
2229
2389
  variants: [
2230
2390
  {
2231
2391
  jsonKey: "question.text",
2392
+ exportJsonKey: [{ "@absent": { question: {} } }, { question: { text: "$" } }],
2232
2393
  tags: [
2233
2394
  {
2234
2395
  key: ConfigKey.group_standardTags,
2235
2396
  description: "Standard tags for the flashcard."
2236
2397
  },
2237
2398
  {
2399
+ exportJsonKey: { title: "$" },
2238
2400
  key: ConfigKey.tag_title,
2239
2401
  description: "Title of the flashcard."
2240
2402
  },
2241
2403
  {
2242
2404
  key: ConfigKey.group_resourceIcon,
2243
2405
  description: "Icon resource for the flashcard.",
2244
- jsonKey: "question.icon|resource(type=image, key=image)"
2406
+ jsonKey: "question.icon|resource(type=image, key=image)",
2407
+ exportJsonKey: { question: { icon: { type: "image", image: { src: "$" } } } }
2408
+ },
2409
+ {
2410
+ key: ConfigKey.property_example,
2411
+ exportJsonKey: [
2412
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2413
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2414
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2415
+ ],
2416
+ description: "Example marker for the flashcard question.",
2417
+ format: TagFormat.bitmarkText,
2418
+ nullable: true
2245
2419
  }
2246
2420
  ]
2247
2421
  }
@@ -2252,37 +2426,56 @@ var CARDSETS = {
2252
2426
  variants: [
2253
2427
  {
2254
2428
  jsonKey: "answer.text",
2429
+ exportJsonKey: [{ "@absent": { answer: {} } }, { answer: { text: "$" } }],
2255
2430
  tags: [
2256
2431
  {
2257
2432
  key: ConfigKey.group_standardTags,
2258
2433
  description: "Standard tags for the flashcard."
2259
2434
  },
2260
2435
  {
2436
+ exportJsonKey: { title: "$" },
2261
2437
  key: ConfigKey.tag_title,
2262
2438
  description: "Title of the flashcard."
2263
2439
  },
2264
2440
  {
2265
2441
  key: ConfigKey.group_resourceIcon,
2266
2442
  description: "Icon resource for the flashcard.",
2267
- jsonKey: "answer.icon|resource(type=image, key=image)"
2443
+ jsonKey: "answer.icon|resource(type=image, key=image)",
2444
+ exportJsonKey: { answer: { icon: { type: "image", image: { src: "$" } } } }
2445
+ },
2446
+ {
2447
+ key: ConfigKey.property_example,
2448
+ exportJsonKey: [
2449
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2450
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2451
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2452
+ ],
2453
+ description: "Example marker for the flashcard answer.",
2454
+ format: TagFormat.bitmarkText,
2455
+ nullable: true
2268
2456
  }
2269
2457
  ]
2270
2458
  },
2271
2459
  {
2272
2460
  jsonKey: "alternativeAnswers[].text",
2461
+ exportJsonKey: { alternativeAnswers: [{ text: "$" }] },
2273
2462
  tags: [
2274
2463
  {
2275
2464
  key: ConfigKey.group_standardTags,
2276
2465
  description: "Standard tags for the flashcard."
2277
2466
  },
2278
2467
  {
2468
+ exportJsonKey: { title: "$" },
2279
2469
  key: ConfigKey.tag_title,
2280
2470
  description: "Title of the flashcard."
2281
2471
  },
2282
2472
  {
2283
2473
  key: ConfigKey.group_resourceIcon,
2284
2474
  description: "Icon resource for the flashcard.",
2285
- jsonKey: "alternativeAnswers[].icon|resource(type=image, key=image)"
2475
+ jsonKey: "alternativeAnswers[].icon|resource(type=image, key=image)",
2476
+ exportJsonKey: {
2477
+ alternativeAnswers: [{ icon: { type: "image", image: { src: "$" } } }]
2478
+ }
2286
2479
  }
2287
2480
  ],
2288
2481
  repeatCount: Count.infinity
@@ -2296,12 +2489,14 @@ var CARDSETS = {
2296
2489
  //
2297
2490
  [CardSetConfigKey.definitionList]: {
2298
2491
  jsonKey: "definitions",
2492
+ exportJsonKey: { definitions: "$" },
2299
2493
  sides: [
2300
2494
  {
2301
2495
  name: "term",
2302
2496
  variants: [
2303
2497
  {
2304
2498
  jsonKey: "term.text",
2499
+ exportJsonKey: [{ "@absent": { term: {} } }, { term: { text: "$" } }],
2305
2500
  tags: [
2306
2501
  {
2307
2502
  key: ConfigKey.group_standardTags,
@@ -2311,13 +2506,26 @@ var CARDSETS = {
2311
2506
  key: ConfigKey.tag_title,
2312
2507
  description: "Title of the definition.",
2313
2508
  format: TagFormat.plainText,
2314
- jsonKey: "^heading.forKeys"
2509
+ jsonKey: "^heading.forKeys",
2510
+ exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
2315
2511
  },
2316
2512
  {
2317
2513
  key: ConfigKey.group_resourceIcon,
2318
2514
  description: "Icon resource for the definition.",
2319
2515
  format: TagFormat.plainText,
2320
- jsonKey: "term.icon|resource(type=image, key=image)"
2516
+ jsonKey: "term.icon|resource(type=image, key=image)",
2517
+ exportJsonKey: { term: { icon: { type: "image", image: { src: "$" } } } }
2518
+ },
2519
+ {
2520
+ key: ConfigKey.property_example,
2521
+ exportJsonKey: [
2522
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2523
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2524
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2525
+ ],
2526
+ description: "Example marker for the definition term.",
2527
+ format: TagFormat.bitmarkText,
2528
+ nullable: true
2321
2529
  }
2322
2530
  ]
2323
2531
  }
@@ -2328,6 +2536,7 @@ var CARDSETS = {
2328
2536
  variants: [
2329
2537
  {
2330
2538
  jsonKey: "definition.text",
2539
+ exportJsonKey: [{ "@absent": { definition: {} } }, { definition: { text: "$" } }],
2331
2540
  tags: [
2332
2541
  {
2333
2542
  key: ConfigKey.group_standardTags,
@@ -2337,30 +2546,48 @@ var CARDSETS = {
2337
2546
  key: ConfigKey.tag_title,
2338
2547
  description: "Title of the definition.",
2339
2548
  format: TagFormat.plainText,
2340
- jsonKey: "^heading.forValues"
2549
+ jsonKey: "^heading.forValues",
2550
+ exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
2341
2551
  },
2342
2552
  {
2343
2553
  key: ConfigKey.group_resourceIcon,
2344
2554
  description: "Icon resource for the definition.",
2345
- jsonKey: "definition.icon|resource(type=image, key=image)"
2555
+ jsonKey: "definition.icon|resource(type=image, key=image)",
2556
+ exportJsonKey: { definition: { icon: { type: "image", image: { src: "$" } } } }
2557
+ },
2558
+ {
2559
+ key: ConfigKey.property_example,
2560
+ exportJsonKey: [
2561
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
2562
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
2563
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2564
+ ],
2565
+ description: "Example marker for the definition.",
2566
+ format: TagFormat.bitmarkText,
2567
+ nullable: true
2346
2568
  }
2347
2569
  ]
2348
2570
  },
2349
2571
  {
2350
2572
  jsonKey: "alternativeDefinitions[].text",
2573
+ exportJsonKey: { alternativeDefinitions: [{ text: "$" }] },
2351
2574
  tags: [
2352
2575
  {
2353
2576
  key: ConfigKey.group_standardTags,
2354
2577
  description: "Standard tags for the definition."
2355
2578
  },
2356
2579
  {
2580
+ exportJsonKey: { title: "$" },
2357
2581
  key: ConfigKey.tag_title,
2358
2582
  description: "Title of the definition."
2359
2583
  },
2360
2584
  {
2361
2585
  key: ConfigKey.group_resourceIcon,
2362
2586
  description: "Icon resource for the definition.",
2363
- jsonKey: "alternativeDefinitions[].icon|resource(type=image, key=image)"
2587
+ jsonKey: "alternativeDefinitions[].icon|resource(type=image, key=image)",
2588
+ exportJsonKey: {
2589
+ alternativeDefinitions: [{ icon: { type: "image", image: { src: "$" } } }]
2590
+ }
2364
2591
  }
2365
2592
  ],
2366
2593
  repeatCount: Count.infinity
@@ -2374,37 +2601,96 @@ var CARDSETS = {
2374
2601
  //
2375
2602
  [CardSetConfigKey.matchPairs]: {
2376
2603
  jsonKey: "pairs",
2604
+ exportJsonKey: { pairs: "$" },
2377
2605
  sides: [
2378
2606
  {
2379
2607
  name: "key",
2380
2608
  variants: [
2381
2609
  {
2382
2610
  jsonKey: "key",
2611
+ exportJsonKey: { key: "$" },
2612
+ format: TextFormat2.plainText,
2383
2613
  tags: [
2384
2614
  {
2385
2615
  key: ConfigKey.group_standardTags,
2386
2616
  description: "Standard tags for the match pair."
2387
2617
  },
2618
+ {
2619
+ // Pair-level example: emit `isExample` alongside `example`
2620
+ // (matches OLD parser's per-pair allow-list behaviour).
2621
+ //
2622
+ // PLAN-074: a valued cascade source (`[@example:V]` anywhere
2623
+ // in the cardset) fills each pair's `example` with `V`
2624
+ // once per pair from the keys-side cascade fire (single
2625
+ // parsed_variant per pair — no per-value duplication).
2626
+ //
2627
+ // PLAN-076 NOTE: matchPairs (TAG 415) KEEPS `example: '$'`
2628
+ // at the pair-object level. BPG `parseMatchPairs`
2629
+ // (`CardContentProcessor.ts:714-723`) emits ONE example
2630
+ // per PAIR (the `exampleCard` accumulator), placed at the
2631
+ // pair object. Unlike matchMatrix (per-cell example),
2632
+ // matchPairs has no nested per-cell layer, so the pair-key
2633
+ // `[@example:V]` must double-emit as the visible field.
2634
+ // Confirmed via `chaining` fixture: `match-solution-grouped`
2635
+ // expects `example: "packen"` at pair-object level from
2636
+ // `die Koffer[%1][@example:packen]`. Original PLAN-076
2637
+ // draft applied to both keys-side tags; the matchPairs
2638
+ // half was reverted after fixture parity testing.
2639
+ key: ConfigKey.property_example,
2640
+ exportJsonKey: [
2641
+ { "@keyonly": { isExample: true, "@bit": { isExample: true } } },
2642
+ {
2643
+ predicates: ["@absent", { $cascade: "*" }],
2644
+ rule: {
2645
+ isExample: true,
2646
+ example: "$cascade",
2647
+ "@bit": { isExample: true }
2648
+ }
2649
+ },
2650
+ { "@absent": { isExample: true } },
2651
+ { isExample: true, example: "$", "@bit": { isExample: true } }
2652
+ ],
2653
+ description: "Example marker / value on the match pair.",
2654
+ format: TagFormat.bitmarkText,
2655
+ nullable: true
2656
+ },
2388
2657
  {
2389
2658
  key: ConfigKey.tag_title,
2390
2659
  description: "Title of the match pair.",
2391
2660
  format: TagFormat.plainText,
2392
- jsonKey: "^heading.forKeys"
2661
+ jsonKey: "^heading.forKeys",
2662
+ exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
2393
2663
  },
2394
2664
  {
2665
+ // PLAN-084: gate the key-side `@absent → $ancestor` cascade
2666
+ // synth with `@variantLacksTag=isCaseSensitive` so the rule
2667
+ // only fires when NO variant in the active card explicitly
2668
+ // carries `[@isCaseSensitive]`. When a value-side variant
2669
+ // carries the tag (e.g. `[@isCaseSensitive:false]`), the
2670
+ // synth is skipped here; the value-side explicit fire owns
2671
+ // the pair-level emission.
2395
2672
  key: ConfigKey.property_isCaseSensitive,
2673
+ exportJsonKey: [
2674
+ {
2675
+ predicates: ["@absent", { "@variantLacksTag": "@isCaseSensitive" }],
2676
+ rule: { isCaseSensitive: "$ancestor" }
2677
+ },
2678
+ { isCaseSensitive: "$" }
2679
+ ],
2396
2680
  description: "Property to indicate if the match is case sensitive.",
2397
2681
  format: TagFormat.boolean
2398
2682
  },
2399
2683
  {
2400
2684
  key: ConfigKey.resource_audio,
2401
2685
  description: "Audio resource for the match pair.",
2402
- jsonKey: "keyAudio"
2686
+ jsonKey: "keyAudio",
2687
+ exportJsonKey: { keyAudio: "$" }
2403
2688
  },
2404
2689
  {
2405
2690
  key: ConfigKey.resource_image,
2406
2691
  description: "Image resource for the match pair.",
2407
- jsonKey: "keyImage"
2692
+ jsonKey: "keyImage",
2693
+ exportJsonKey: { keyImage: "$" }
2408
2694
  }
2409
2695
  ]
2410
2696
  }
@@ -2416,19 +2702,104 @@ var CARDSETS = {
2416
2702
  variants: [
2417
2703
  {
2418
2704
  jsonKey: "values[]",
2705
+ exportJsonKey: { values: ["$"] },
2706
+ format: TextFormat2.plainText,
2419
2707
  tags: [
2420
2708
  {
2421
- key: ConfigKey.group_standardTags,
2422
- description: "Standard tags for the match pair."
2709
+ // PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
2710
+ // BPG `parseMatchPairs` does `delete tags.item; delete tags.lead;`
2711
+ // for sideIdx > 0; the schema-side equivalent is to use a
2712
+ // group_standardTags variant that omits group_standardItemLead.
2713
+ key: ConfigKey.group_standardTagsNoItemLead,
2714
+ description: "Standard tags for the match pair (values side, no item/lead)."
2715
+ },
2716
+ {
2717
+ // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.6: cascade-fired
2718
+ // `@example` from the bit header fills each pair's
2719
+ // `example` with the pair's first value (mirrors BPG's
2720
+ // `fillStringExample(pairs, __defaultExample=values[0])`).
2721
+ //
2722
+ // PLAN-074: the cascade-source-VALUE branch is handled by
2723
+ // TAG 415 (keys side) which fires once per pair — keeping
2724
+ // it here would emit `example` twice per pair (one per
2725
+ // values-side parsed_variant) and merge them into a
2726
+ // multi-paragraph array.
2727
+ //
2728
+ // PLAN-075: bare `$parent` reads the parent variant fire's
2729
+ // source `$` value (the current `++`-iteration's value
2730
+ // text). For single-value pairs this matches the old
2731
+ // `$parent.values[0]`; for multi-value pairs with
2732
+ // `[@example]` attached to a non-first value, it correctly
2733
+ // resolves to that value.
2734
+ //
2735
+ // PLAN-077: dual `[@absent, { '$cascade': '*' }]` rule
2736
+ // routes pair-key `[@example:V]` (a per-pair cascade
2737
+ // source, bound via per-card CascadeCounters scope) and
2738
+ // bit-header `[@example:V]` (cardset cascade) into the
2739
+ // pair-level `example` via `$cascade`. The aggregator in
2740
+ // `populate_variants` fires this rule ONCE per pair (BPG
2741
+ // `parseMatchPairs` `exampleCard` precedence), avoiding
2742
+ // per-iter pollution.
2743
+ key: ConfigKey.property_example,
2744
+ exportJsonKey: [
2745
+ // PLAN-084: bare `[@example]` on a values-side variant fires
2746
+ // once per pair (first-wins, mirrors BPG `parseMatchPairs`
2747
+ // `exampleCard` + `pushDownTree` set-if-null semantic).
2748
+ // `maxEmits: 1` enforced inside the per-pair cascade-counter
2749
+ // scope opened by `build_card_item`.
2750
+ {
2751
+ predicates: ["@keyonly"],
2752
+ maxEmits: 1,
2753
+ rule: {
2754
+ isExample: true,
2755
+ example: "$parent",
2756
+ "@bit": { isExample: true }
2757
+ }
2758
+ },
2759
+ {
2760
+ predicates: ["@absent", { $cascade: "*" }],
2761
+ rule: {
2762
+ isExample: true,
2763
+ example: "$cascade",
2764
+ "@bit": { isExample: true }
2765
+ }
2766
+ },
2767
+ {
2768
+ "@absent": {
2769
+ isExample: true,
2770
+ example: "$parent",
2771
+ "@bit": { isExample: true }
2772
+ }
2773
+ },
2774
+ // PLAN-084: same cap applies to the explicit-value `[@example:V]`
2775
+ // form so per-`++`-iter values-side fires also first-win.
2776
+ {
2777
+ predicates: [],
2778
+ maxEmits: 1,
2779
+ rule: { isExample: true, example: "$", "@bit": { isExample: true } }
2780
+ }
2781
+ ],
2782
+ description: "Example marker / value on the match pair (values side).",
2783
+ format: TagFormat.bitmarkText,
2784
+ nullable: true
2423
2785
  },
2424
2786
  {
2425
2787
  key: ConfigKey.tag_title,
2426
2788
  description: "Title of the match pair.",
2427
2789
  format: TagFormat.plainText,
2428
- jsonKey: "^heading.forValues"
2790
+ jsonKey: "^heading.forValues",
2791
+ exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
2429
2792
  },
2430
2793
  {
2794
+ // PLAN-084: symmetric @variantLacksTag gating with the key-side.
2431
2795
  key: ConfigKey.property_isCaseSensitive,
2796
+ exportJsonKey: [
2797
+ {
2798
+ predicates: ["@absent", { "@variantLacksTag": "@isCaseSensitive" }],
2799
+ rule: { isCaseSensitive: "$ancestor" }
2800
+ },
2801
+ { isCaseSensitive: "$" }
2802
+ ],
2432
2803
  description: "Property to indicate if the match is case sensitive.",
2433
2804
  format: TagFormat.boolean
2434
2805
  }
@@ -2444,12 +2815,15 @@ var CARDSETS = {
2444
2815
  //
2445
2816
  [CardSetConfigKey.matchAudioPairs]: {
2446
2817
  jsonKey: "pairs",
2818
+ exportJsonKey: { pairs: "$" },
2447
2819
  sides: [
2448
2820
  {
2449
2821
  name: "key",
2450
2822
  variants: [
2451
2823
  {
2452
2824
  jsonKey: "key",
2825
+ exportJsonKey: { key: "$" },
2826
+ format: TextFormat2.plainText,
2453
2827
  tags: [
2454
2828
  {
2455
2829
  key: ConfigKey.group_standardTags,
@@ -2459,12 +2833,14 @@ var CARDSETS = {
2459
2833
  key: ConfigKey.tag_title,
2460
2834
  description: "Title of the audio match pair.",
2461
2835
  format: TagFormat.plainText,
2462
- jsonKey: "^heading.forKeys"
2836
+ jsonKey: "^heading.forKeys",
2837
+ exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
2463
2838
  },
2464
2839
  {
2465
2840
  key: ConfigKey.resource_audio,
2466
2841
  description: "Audio resource for the match pair.",
2467
- jsonKey: "keyAudio"
2842
+ jsonKey: "keyAudio",
2843
+ exportJsonKey: { keyAudio: "$" }
2468
2844
  }
2469
2845
  ]
2470
2846
  }
@@ -2476,16 +2852,20 @@ var CARDSETS = {
2476
2852
  variants: [
2477
2853
  {
2478
2854
  jsonKey: "values[]",
2855
+ exportJsonKey: { values: ["$"] },
2856
+ format: TextFormat2.plainText,
2479
2857
  tags: [
2480
2858
  {
2481
- key: ConfigKey.group_standardTags,
2482
- description: "Standard tags for the audio match pair."
2859
+ // PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
2860
+ key: ConfigKey.group_standardTagsNoItemLead,
2861
+ description: "Standard tags for the audio match pair (values side, no item/lead)."
2483
2862
  },
2484
2863
  {
2485
2864
  key: ConfigKey.tag_title,
2486
2865
  description: "Title of the audio match pair.",
2487
2866
  format: TagFormat.plainText,
2488
- jsonKey: "^heading.forValues"
2867
+ jsonKey: "^heading.forValues",
2868
+ exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
2489
2869
  },
2490
2870
  {
2491
2871
  key: ConfigKey.resource_audio,
@@ -2503,12 +2883,15 @@ var CARDSETS = {
2503
2883
  //
2504
2884
  [CardSetConfigKey.matchImagePairs]: {
2505
2885
  jsonKey: "pairs",
2886
+ exportJsonKey: { pairs: "$" },
2506
2887
  sides: [
2507
2888
  {
2508
2889
  name: "key",
2509
2890
  variants: [
2510
2891
  {
2511
2892
  jsonKey: "key",
2893
+ exportJsonKey: { key: "$" },
2894
+ format: TextFormat2.plainText,
2512
2895
  tags: [
2513
2896
  {
2514
2897
  key: ConfigKey.group_standardTags,
@@ -2518,12 +2901,14 @@ var CARDSETS = {
2518
2901
  key: ConfigKey.tag_title,
2519
2902
  description: "Title of the image match pair.",
2520
2903
  format: TagFormat.plainText,
2521
- jsonKey: "^heading.forKeys"
2904
+ jsonKey: "^heading.forKeys",
2905
+ exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
2522
2906
  },
2523
2907
  {
2524
2908
  key: ConfigKey.resource_image,
2525
2909
  description: "Image resource for the match pair.",
2526
- jsonKey: "keyImage"
2910
+ jsonKey: "keyImage",
2911
+ exportJsonKey: { keyImage: "$" }
2527
2912
  }
2528
2913
  ]
2529
2914
  }
@@ -2535,16 +2920,20 @@ var CARDSETS = {
2535
2920
  variants: [
2536
2921
  {
2537
2922
  jsonKey: "values[]",
2923
+ exportJsonKey: { values: ["$"] },
2924
+ format: TextFormat2.plainText,
2538
2925
  tags: [
2539
2926
  {
2540
- key: ConfigKey.group_standardTags,
2541
- description: "Standard tags for the image match pair."
2927
+ // PLAN-084: value-side excludes item/lead/pageNumber/marginNumber.
2928
+ key: ConfigKey.group_standardTagsNoItemLead,
2929
+ description: "Standard tags for the image match pair (values side, no item/lead)."
2542
2930
  },
2543
2931
  {
2544
2932
  key: ConfigKey.tag_title,
2545
2933
  description: "Title of the image match pair.",
2546
2934
  format: TagFormat.plainText,
2547
- jsonKey: "^heading.forValues"
2935
+ jsonKey: "^heading.forValues",
2936
+ exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
2548
2937
  },
2549
2938
  {
2550
2939
  key: ConfigKey.resource_image,
@@ -2562,30 +2951,71 @@ var CARDSETS = {
2562
2951
  //
2563
2952
  [CardSetConfigKey.matchMatrix]: {
2564
2953
  jsonKey: "matrix",
2954
+ exportJsonKey: { matrix: "$" },
2565
2955
  sides: [
2566
2956
  {
2567
2957
  name: "key",
2568
2958
  variants: [
2569
2959
  {
2570
2960
  jsonKey: "key",
2961
+ exportJsonKey: { key: "$" },
2962
+ format: TextFormat2.plainText,
2571
2963
  tags: [
2572
2964
  {
2573
2965
  key: ConfigKey.group_standardItemLeadInstructionHint,
2574
2966
  description: "Standard tags for lead, instruction, and hint."
2575
2967
  },
2576
2968
  {
2969
+ // PLAN-076: drop `example: '$'` from valued rule — pair-key
2970
+ // `[@example:V]` is consumed as the per-pair cascade source.
2971
+ // PLAN-077 R14 follow-up: investigation showed the fixture
2972
+ // reference is INCONSISTENT — bits without a cardset-level
2973
+ // cascade (e.g. bit 3 `[@example:Y]should be...`) expect
2974
+ // pair-level `example: V` at pair scope, but bits WITH a
2975
+ // cardset cascade (e.g. bit 8 `[@example:99]` in title-row
2976
+ // PLUS pair-key `[@example:Y]`) expect NO pair-level
2977
+ // example. Emitting `example: $` here satisfies bit 3 but
2978
+ // breaks bit 8; dropping it satisfies bit 8 but breaks bit
2979
+ // 3. Conditional emission would require runtime gating on
2980
+ // the cardset cascade scope state. Left dropped pending a
2981
+ // dedicated plan — accepted divergence on bit 3's pair-
2982
+ // level emission (cells still get the per-cell `example`
2983
+ // correctly via PLAN-077 §4.2 + §4.3).
2577
2984
  key: ConfigKey.property_example,
2985
+ exportJsonKey: [
2986
+ {
2987
+ "@keyonly": {
2988
+ isExample: true,
2989
+ "@bit": { isExample: true },
2990
+ "@card": { isExample: true }
2991
+ }
2992
+ },
2993
+ {
2994
+ "@absent": {
2995
+ isExample: true,
2996
+ "@bit": { isExample: true },
2997
+ "@card": { isExample: true }
2998
+ }
2999
+ },
3000
+ { isExample: true, "@bit": { isExample: true }, "@card": { isExample: true } }
3001
+ ],
2578
3002
  description: "Example text for the match matrix.",
2579
- format: TagFormat.plainText
3003
+ format: TagFormat.plainText,
3004
+ nullable: true
2580
3005
  },
2581
3006
  {
2582
3007
  key: ConfigKey.tag_title,
2583
3008
  description: "Title of the match matrix.",
2584
3009
  format: TagFormat.plainText,
2585
- jsonKey: "^heading.forKeys"
3010
+ jsonKey: "^heading.forKeys",
3011
+ exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
2586
3012
  },
2587
3013
  {
2588
3014
  key: ConfigKey.property_isCaseSensitive,
3015
+ exportJsonKey: [
3016
+ { "@absent": { isCaseSensitive: "$ancestor" } },
3017
+ { isCaseSensitive: "$" }
3018
+ ],
2589
3019
  description: "Property to indicate if the match is case sensitive.",
2590
3020
  format: TagFormat.boolean
2591
3021
  }
@@ -2599,24 +3029,103 @@ var CARDSETS = {
2599
3029
  variants: [
2600
3030
  {
2601
3031
  jsonKey: "cells[{s}].values[]",
3032
+ exportJsonKey: [
3033
+ // PLAN-085 / R14: the positional-blank emission for an
3034
+ // effectively-absent cell side fires ONLY when the active
3035
+ // card has at least one `[#]` variant fire. This matches
3036
+ // BPG `extractHeadingCard`'s `isHeading` gate: when no
3037
+ // `[#]` exists anywhere in the title card, BPG returns
3038
+ // undefined and no `heading` is emitted. The phantom
3039
+ // `forValues:['']` placeholder is appropriate only when
3040
+ // the card is being treated as a title-row.
3041
+ {
3042
+ predicates: ["@absent", { "@variantHasTag": "#" }],
3043
+ rule: { "@bit": { heading: { forValues: [""] } } }
3044
+ },
3045
+ { cells: { $s: { values: ["$"] } } }
3046
+ ],
3047
+ format: TextFormat2.plainText,
2602
3048
  tags: [
2603
3049
  {
2604
3050
  key: ConfigKey.group_standardItemLeadInstructionHint,
2605
3051
  description: "Standard tags for lead, instruction, and hint."
2606
3052
  },
2607
3053
  {
3054
+ // PLAN-075: bare `$parent` reads the parent variant fire's
3055
+ // source `$` value (the current `++`-iteration's cell text).
3056
+ // Replaces `$parent.values[0]` which pinned to position 0
3057
+ // regardless of which `++`-value `[@example]` was on.
3058
+ //
3059
+ // PLAN-077: dual `[@absent, { '$cascade': '*' }]` rule
3060
+ // routes pair-key `[@example:V]` (a per-pair cascade
3061
+ // source, bound via per-card CascadeCounters scope) and
3062
+ // bit-header `[@example:V]` (cardset cascade) into the
3063
+ // per-cell `example` via `$cascade`. The aggregator in
3064
+ // `populate_variants` fires this rule ONCE per cell (BPG
3065
+ // `parseMatchMatrix` `exampleSide` precedence), avoiding
3066
+ // per-iter pollution.
3067
+ // @card scope-shift writes matrix-row container isExample.
2608
3068
  key: ConfigKey.property_example,
3069
+ exportJsonKey: [
3070
+ {
3071
+ "@keyonly": {
3072
+ cells: { $s: { isExample: true, example: "$parent" } },
3073
+ "@bit": { isExample: true },
3074
+ "@card": { isExample: true }
3075
+ }
3076
+ },
3077
+ {
3078
+ predicates: ["@absent", { $cascade: "*" }],
3079
+ rule: {
3080
+ cells: { $s: { isExample: true, example: "$cascade" } },
3081
+ "@bit": { isExample: true },
3082
+ "@card": { isExample: true }
3083
+ }
3084
+ },
3085
+ {
3086
+ "@absent": {
3087
+ cells: { $s: { isExample: true, example: "$parent" } },
3088
+ "@bit": { isExample: true },
3089
+ "@card": { isExample: true }
3090
+ }
3091
+ },
3092
+ {
3093
+ cells: { $s: { isExample: true, example: "$" } },
3094
+ "@bit": { isExample: true },
3095
+ "@card": { isExample: true }
3096
+ }
3097
+ ],
2609
3098
  description: "Example text for the match matrix.",
2610
- format: TagFormat.plainText
3099
+ format: TagFormat.bitmarkText,
3100
+ nullable: true
2611
3101
  },
2612
3102
  {
3103
+ // PLAN-085 / R14: bare `[#]` on a value-side emits the
3104
+ // empty-string positional blank into `forValues` so the
3105
+ // array stays aligned with the cell positions (matches BPG
3106
+ // `extractHeadingCard`'s "all value-side headings present"
3107
+ // shape). The `@keyonly` rule is required for two reasons:
3108
+ // 1) it provides an emission for bare `[#]` (the `$` sigil
3109
+ // in the unconditional rule has no source for bare); and
3110
+ // 2) the presence of any `@keyonly` rule disables the
3111
+ // optimised-mode `should_suppress_outcome` strip (see
3112
+ // `forward_emit.rs`) so the resulting `forValues: [""]`
3113
+ // survives the all-natural-defaults check.
2613
3114
  key: ConfigKey.tag_title,
2614
3115
  description: "Title of the match matrix.",
2615
3116
  format: TagFormat.plainText,
2616
- jsonKey: "^heading.forValues"
3117
+ jsonKey: "^heading.forValues",
3118
+ exportJsonKey: [
3119
+ { "@keyonly": { "@bit": { heading: { forValues: [""] } } } },
3120
+ { "@bit": { heading: { forValues: ["$"] } } }
3121
+ ]
2617
3122
  },
2618
3123
  {
2619
3124
  key: ConfigKey.property_isCaseSensitive,
3125
+ exportJsonKey: [
3126
+ { "@absent": { isCaseSensitive: "$ancestor" } },
3127
+ { isCaseSensitive: "$" }
3128
+ ],
2620
3129
  description: "Property to indicate if the match is case sensitive.",
2621
3130
  format: TagFormat.boolean
2622
3131
  }
@@ -2632,28 +3141,61 @@ var CARDSETS = {
2632
3141
  //
2633
3142
  [CardSetConfigKey.statements]: {
2634
3143
  jsonKey: "statements",
3144
+ exportJsonKey: { statements: "$" },
2635
3145
  sides: [
2636
3146
  {
2637
3147
  name: "statement",
2638
3148
  variants: [
2639
3149
  {
2640
3150
  jsonKey: "statement",
3151
+ exportJsonKey: { statement: "$" },
2641
3152
  tags: [
2642
3153
  {
2643
3154
  key: ConfigKey.tag_true,
2644
3155
  description: "Tag for true statements.",
2645
3156
  jsonKey: "statement|set(isCorrect=true)",
3157
+ exportJsonKey: { statement: "$", isCorrect: true },
2646
3158
  maxCount: 1
2647
3159
  },
2648
3160
  {
2649
3161
  key: ConfigKey.tag_false,
2650
3162
  description: "Tag for false statements.",
2651
3163
  jsonKey: "statement|set(isCorrect=false)",
3164
+ exportJsonKey: { statement: "$", isCorrect: false },
2652
3165
  maxCount: 1
2653
3166
  },
2654
3167
  {
2655
3168
  key: ConfigKey.group_standardTags,
2656
3169
  description: "Standard tags for the statement."
3170
+ },
3171
+ {
3172
+ // PLAN-072: cascade-fired `@example` from the bit header
3173
+ // fills each statement entry's `example` with the entry's
3174
+ // own `isCorrect`, mirroring BPG's `pushExampleDownTree`
3175
+ // for the statements cardset. Per-array-item strip removes
3176
+ // `example: false` from incorrect entries so only correct
3177
+ // entries retain `example: true`.
3178
+ key: ConfigKey.property_example,
3179
+ exportJsonKey: [
3180
+ {
3181
+ "@keyonly": {
3182
+ isExample: true,
3183
+ example: true,
3184
+ "@bit": { isExample: true }
3185
+ }
3186
+ },
3187
+ {
3188
+ "@absent": {
3189
+ isExample: true,
3190
+ example: "$parent.isCorrect",
3191
+ "@bit": { isExample: true }
3192
+ }
3193
+ },
3194
+ { isExample: true, example: "$", "@bit": { isExample: true } }
3195
+ ],
3196
+ description: "Example marker on the statement. Bare `[@example]` emits the boolean marker; valued `[@example:true|false]` emits the literal value. Cascade from bit-header fills `example` with the entry\u2019s own `isCorrect`.",
3197
+ format: TagFormat.boolean,
3198
+ nullable: true
2657
3199
  }
2658
3200
  ],
2659
3201
  bodyAllowed: false
@@ -2667,6 +3209,7 @@ var CARDSETS = {
2667
3209
  //
2668
3210
  [CardSetConfigKey.quiz]: {
2669
3211
  jsonKey: "quizzes",
3212
+ exportJsonKey: { quizzes: "$" },
2670
3213
  sides: [
2671
3214
  {
2672
3215
  name: "choices",
@@ -2681,6 +3224,71 @@ var CARDSETS = {
2681
3224
  {
2682
3225
  key: ConfigKey.group_standardTags,
2683
3226
  description: "Standard tags for the quiz."
3227
+ },
3228
+ {
3229
+ // ALIGN-EXAMPLE-CASCADE §3.2: variant-level cascade for
3230
+ // quiz choices is delegated to group_trueFalse's
3231
+ // chain-child `@example` (`+` chain with maxEmits:1 +
3232
+ // `@parent.isCorrect=true`). Variant-level `@absent`
3233
+ // would over-fire on subsequent correct choices and on
3234
+ // incorrect ones — drop it.
3235
+ key: ConfigKey.property_example,
3236
+ exportJsonKey: [
3237
+ { "@keyonly": { isExample: true, "@bit": { isExample: true } } },
3238
+ { isExample: true, "@bit": { isExample: true } }
3239
+ ],
3240
+ description: "Example marker on the quiz choice (entry-local only; cascade handled by group_trueFalse chain-children).",
3241
+ format: TagFormat.boolean,
3242
+ nullable: true
3243
+ }
3244
+ ],
3245
+ bodyAllowed: false
3246
+ }
3247
+ ]
3248
+ }
3249
+ ]
3250
+ },
3251
+ //
3252
+ // quiz (responses)
3253
+ //
3254
+ [CardSetConfigKey.quizResponses]: {
3255
+ jsonKey: "quizzes",
3256
+ exportJsonKey: { quizzes: "$" },
3257
+ sides: [
3258
+ {
3259
+ name: "responses",
3260
+ variants: [
3261
+ {
3262
+ jsonKey: null,
3263
+ tags: [
3264
+ {
3265
+ key: ConfigKey.group_trueFalseResponses,
3266
+ description: "Group for true/false questions."
3267
+ },
3268
+ {
3269
+ key: ConfigKey.group_standardTags,
3270
+ description: "Standard tags for the quiz."
3271
+ },
3272
+ {
3273
+ // PLAN-072: cascade-fired `@example` from the bit header
3274
+ // fills each response entry's `example` with the entry's
3275
+ // own `isCorrect`, mirroring BPG's `pushExampleDownTree`
3276
+ // for the quiz-responses cardset (statements-style).
3277
+ key: ConfigKey.property_example,
3278
+ exportJsonKey: [
3279
+ { "@keyonly": { isExample: true, "@bit": { isExample: true } } },
3280
+ {
3281
+ "@absent": {
3282
+ isExample: true,
3283
+ example: "$parent.isCorrect",
3284
+ "@bit": { isExample: true }
3285
+ }
3286
+ },
3287
+ { isExample: true, "@bit": { isExample: true } }
3288
+ ],
3289
+ description: "Example marker on the quiz response; cascade from bit-header fills `example` with the entry\u2019s own `isCorrect`.",
3290
+ format: TagFormat.boolean,
3291
+ nullable: true
2684
3292
  }
2685
3293
  ],
2686
3294
  bodyAllowed: false
@@ -2694,6 +3302,7 @@ var CARDSETS = {
2694
3302
  //
2695
3303
  [CardSetConfigKey.feedback]: {
2696
3304
  jsonKey: "feedbacks",
3305
+ exportJsonKey: { feedbacks: "$" },
2697
3306
  sides: [
2698
3307
  {
2699
3308
  name: "choices",
@@ -2705,6 +3314,73 @@ var CARDSETS = {
2705
3314
  key: ConfigKey.group_standardItemLeadInstructionHint,
2706
3315
  description: "Standard tags for lead, instruction, and hint."
2707
3316
  },
3317
+ {
3318
+ // Feedback choices: `[+]` / `[-]` map to choice + `requireReason`
3319
+ // (NOT `isCorrect` like trueFalse). Shadow the inherited
3320
+ // `tag_true` / `tag_false` from `group_trueFalse` to emit the
3321
+ // feedback-specific shape.
3322
+ //
3323
+ // Chain @example: `[+ X][@example]` writes
3324
+ // `isExample/example` onto the X choice only. BPG's
3325
+ // `setIsExampleFlags` (Builder.ts:4041-4172) does NOT
3326
+ // iterate `cardNode.feedbacks` — so unlike multipleChoice,
3327
+ // there is no bubble to bit-level or feedbacks-level
3328
+ // isExample. Pattern omits `@bit` scope-shift accordingly.
3329
+ // No `@absent`/`@parent.isCorrect` cascade rules either:
3330
+ // BPG's `pushExampleDownTree` (Builder.ts:3777-3849) has
3331
+ // no feedback dispatch, so bit-header `[@example]` does
3332
+ // not flow into feedback choices.
3333
+ key: ConfigKey.tag_true,
3334
+ exportJsonKey: { choices: [{ choice: "$", requireReason: true }] },
3335
+ description: "Reason-required choice for feedback (`[+]`).",
3336
+ maxCount: Count.infinity,
3337
+ format: TagFormat.plainText,
3338
+ chain: [
3339
+ {
3340
+ key: ConfigKey.property_example,
3341
+ exportJsonKey: [
3342
+ { "@keyonly": { isExample: true, example: true } },
3343
+ { isExample: true, example: "$" }
3344
+ ],
3345
+ description: "Per-choice example marker for feedback `[+]`.",
3346
+ format: TagFormat.boolean,
3347
+ maxCount: 1,
3348
+ nullable: true
3349
+ },
3350
+ {
3351
+ // `[+ X][! foo]` chains `!` (instruction) onto the
3352
+ // choice; same for `[?]` (hint), `[%]` (item),
3353
+ // `[%2]`/`[%3]`/`[%4]` (lead/pageNumber/marginNumber).
3354
+ // Mirrors group_trueFalse's `+` chain shape.
3355
+ key: ConfigKey.group_standardItemLeadInstructionHint,
3356
+ description: "Item, lead, page number, margin number, instruction and hint chained onto the feedback choice."
3357
+ }
3358
+ ]
3359
+ },
3360
+ {
3361
+ key: ConfigKey.tag_false,
3362
+ exportJsonKey: { choices: [{ choice: "$", requireReason: false }] },
3363
+ description: "Reason-not-required choice for feedback (`[-]`).",
3364
+ maxCount: Count.infinity,
3365
+ format: TagFormat.plainText,
3366
+ chain: [
3367
+ {
3368
+ key: ConfigKey.property_example,
3369
+ exportJsonKey: [
3370
+ { "@keyonly": { isExample: true, example: true } },
3371
+ { isExample: true, example: "$" }
3372
+ ],
3373
+ description: "Per-choice example marker for feedback `[-]`.",
3374
+ format: TagFormat.boolean,
3375
+ maxCount: 1,
3376
+ nullable: true
3377
+ },
3378
+ {
3379
+ key: ConfigKey.group_standardItemLeadInstructionHint,
3380
+ description: "Item, lead, page number, margin number, instruction and hint chained onto the feedback choice."
3381
+ }
3382
+ ]
3383
+ },
2708
3384
  {
2709
3385
  key: ConfigKey.group_trueFalse,
2710
3386
  description: "Group for true/false feedback."
@@ -2713,7 +3389,8 @@ var CARDSETS = {
2713
3389
  key: ConfigKey.tag_title,
2714
3390
  description: "Title of the feedback.",
2715
3391
  format: TagFormat.plainText,
2716
- jsonKey: "^heading.forKeys"
3392
+ jsonKey: "^heading.forKeys",
3393
+ exportJsonKey: { "@bit": { heading: { forKeys: "$" } } }
2717
3394
  }
2718
3395
  ],
2719
3396
  bodyAllowed: false
@@ -2724,27 +3401,78 @@ var CARDSETS = {
2724
3401
  name: "reason",
2725
3402
  variants: [
2726
3403
  {
3404
+ // PLAN: feedback.reason — Object-only variant pattern. Per
3405
+ // SYNTAX.md §8.1, sibling tag emissions do NOT auto-wrap
3406
+ // under `reason.*`; each tag here spells out the full path.
3407
+ // Body is a plain string (BPG `parseFeedback` uses
3408
+ // `cardBodyStr ?? ''` for `reason.text`).
2727
3409
  jsonKey: "reason.text",
3410
+ exportJsonKey: { reason: { text: "$" } },
3411
+ format: TextFormat2.plainText,
2728
3412
  tags: [
2729
3413
  {
2730
- key: ConfigKey.group_standardItemLeadInstructionHint,
2731
- description: "Standard tags for lead, instruction, and hint."
3414
+ // Instruction lands under reason. Hint/item/lead/etc.
3415
+ // omitted: not exercised by any fixture and BPG's
3416
+ // `parseFeedback` spreads only the tags actually present
3417
+ // — adding the rest can be done lazily as fixtures
3418
+ // require them, with the same `{reason: {…}}` wrapper.
3419
+ key: ConfigKey.tag_instruction,
3420
+ exportJsonKey: { reason: { instruction: "$" } },
3421
+ description: "Instruction for the feedback reason.",
3422
+ format: TagFormat.bitmarkText
2732
3423
  },
2733
3424
  {
2734
3425
  key: ConfigKey.property_reasonableNumOfChars,
3426
+ exportJsonKey: [
3427
+ { "@absent": { reason: { reasonableNumOfChars: "$ancestor" } } },
3428
+ { reason: { reasonableNumOfChars: "$" } }
3429
+ ],
2735
3430
  description: "Property for reasonable number of characters.",
2736
3431
  format: TagFormat.number
2737
3432
  },
2738
3433
  {
3434
+ // ALIGN-EXAMPLE-CASCADE §3.9: `feedback.reason` is NOT in
3435
+ // BPG's `pushExampleDownTree` dispatch — no bit-header
3436
+ // cascade. Local emission only.
3437
+ //
3438
+ // Both `reason.example` and `feedbacks[].example` are
3439
+ // `TextAst` per BPG's schema — paragraph arrays. Format
3440
+ // here is `bitmarkText` so `$` coerces to a paragraph
3441
+ // array at both write sites.
2739
3442
  key: ConfigKey.property_example,
2740
- description: "Example text for the feedback.",
2741
- format: TagFormat.plainText
3443
+ exportJsonKey: [
3444
+ {
3445
+ // Bare `[@example]`: reason.example is TextAst — BPG
3446
+ // coerces the boolean default to a paragraph
3447
+ // containing the string "true". feedbacks[].example
3448
+ // is not TextAst, stays a boolean. Two literal writes.
3449
+ "@keyonly": {
3450
+ reason: {
3451
+ isExample: true,
3452
+ example: [{ type: "paragraph", content: [{ text: "true", type: "text" }] }]
3453
+ },
3454
+ isExample: true,
3455
+ example: true,
3456
+ "@bit": { isExample: true }
3457
+ }
3458
+ },
3459
+ {
3460
+ reason: { isExample: true, example: "$" },
3461
+ isExample: true,
3462
+ example: "$",
3463
+ "@bit": { isExample: true }
3464
+ }
3465
+ ],
3466
+ description: "Example text for the feedback reason (entry-local only; no bit-header cascade).",
3467
+ format: TagFormat.bitmarkText,
3468
+ nullable: true
2742
3469
  },
2743
3470
  {
2744
3471
  key: ConfigKey.tag_title,
2745
3472
  description: "Title of the feedback.",
2746
3473
  format: TagFormat.plainText,
2747
- jsonKey: "^heading.forValues"
3474
+ jsonKey: "^heading.forValues",
3475
+ exportJsonKey: { "@bit": { heading: { forValues: "$" } } }
2748
3476
  }
2749
3477
  ],
2750
3478
  bodyAllowed: true
@@ -2758,21 +3486,29 @@ var CARDSETS = {
2758
3486
  //
2759
3487
  [CardSetConfigKey.questions]: {
2760
3488
  jsonKey: "questions",
3489
+ exportJsonKey: { questions: "$" },
2761
3490
  sides: [
2762
3491
  {
2763
3492
  name: "question",
2764
3493
  variants: [
2765
3494
  {
2766
3495
  jsonKey: "question",
3496
+ exportJsonKey: { question: "$" },
2767
3497
  tags: [
2768
3498
  {
2769
3499
  key: ConfigKey.property_reasonableNumOfChars,
3500
+ exportJsonKey: [
3501
+ { "@absent": { reasonableNumOfChars: "$ancestor" } },
3502
+ { reasonableNumOfChars: "$" }
3503
+ ],
2770
3504
  description: "Property for reasonable number of characters.",
2771
3505
  format: TagFormat.number
2772
3506
  },
2773
3507
  {
3508
+ exportJsonKey: { sampleSolution: "$" },
2774
3509
  key: ConfigKey.tag_sampleSolution,
2775
- description: "Sample solution for the question."
3510
+ description: "Sample solution for the question.",
3511
+ format: TagFormat.plainText
2776
3512
  },
2777
3513
  {
2778
3514
  key: ConfigKey.property_sampleSolution,
@@ -2793,6 +3529,22 @@ var CARDSETS = {
2793
3529
  {
2794
3530
  key: ConfigKey.group_standardTags,
2795
3531
  description: "Standard tags for the question."
3532
+ },
3533
+ {
3534
+ // ALIGN-EXAMPLE-CASCADE §3.10: `questions` is NOT in BPG's
3535
+ // `pushExampleDownTree` dispatch — bit-header `[@example]`
3536
+ // does not propagate `example`/`isExample` onto question
3537
+ // entries. Only entry-local `[@example]` fires (handled by
3538
+ // `@keyonly` and the unconditional fallback). No `@absent`
3539
+ // rule.
3540
+ key: ConfigKey.property_example,
3541
+ exportJsonKey: [
3542
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
3543
+ { isExample: true, example: "$", "@bit": { isExample: true } }
3544
+ ],
3545
+ description: "Example marker for the question (entry-local only; no bit-header cascade).",
3546
+ format: TagFormat.bitmarkText,
3547
+ nullable: true
2796
3548
  }
2797
3549
  ]
2798
3550
  }
@@ -2812,15 +3564,23 @@ var CARDSETS = {
2812
3564
  variants: [
2813
3565
  {
2814
3566
  jsonKey: "elements[]",
3567
+ exportJsonKey: { elements: ["$"] },
2815
3568
  tags: [
2816
3569
  {
2817
3570
  key: ConfigKey.group_standardItemLeadInstructionHint,
2818
3571
  description: "Standard tags for lead, instruction, and hint."
2819
3572
  },
2820
3573
  {
3574
+ // ALIGN-EXAMPLE-CASCADE §3.11: `elements` is NOT in BPG's
3575
+ // `pushExampleDownTree` dispatch — no bit-header cascade.
2821
3576
  key: ConfigKey.property_example,
2822
- description: "Example text for the element.",
2823
- format: TagFormat.plainText
3577
+ exportJsonKey: [
3578
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
3579
+ { isExample: true, example: "$", "@bit": { isExample: true } }
3580
+ ],
3581
+ description: "Example text for the element (entry-local only; no bit-header cascade).",
3582
+ format: TagFormat.plainText,
3583
+ nullable: true
2824
3584
  }
2825
3585
  ],
2826
3586
  repeatCount: Count.infinity
@@ -2832,8 +3592,57 @@ var CARDSETS = {
2832
3592
  //
2833
3593
  // table
2834
3594
  //
3595
+ // PLAN-079: supports BOTH source formats and collapses to the same basic
3596
+ // output (`{ columns?, data, columnWidths? }`):
3597
+ //
3598
+ // 1. Basic format — first card carries `[#X]` markers (fire `tag_title`
3599
+ // → `^table.columns[]`); subsequent cards are body rows. Cards have
3600
+ // no qualifier, so they route to the `table-body` (default) section.
3601
+ //
3602
+ // 2. Extended format — `==== table-header ====` / `==== table-footer ====`
3603
+ // qualifiers. The `table-header` section uses `@cardIndex=0` to route
3604
+ // the first header card's cells to `columns` and demote remaining
3605
+ // header cards (rows) into `data`. Per-cell `[@tableRowSpan]` /
3606
+ // `[@tableScope]` / `[@tableCellType]` tags are valid-but-unmapped
3607
+ // here (no per-cell metadata in basic shape) — declared on the cell
3608
+ // variant below.
3609
+ //
2835
3610
  [CardSetConfigKey.table]: {
2836
- jsonKey: "table.data",
3611
+ jsonKey: null,
3612
+ exportJsonKey: null,
3613
+ sections: {
3614
+ "table-header": {
3615
+ // First header card's row of cells → `columns`. Subsequent header
3616
+ // cards' rows → `data` (per-card append; `[$]` shape because
3617
+ // per-card emission fires once per card, multiple fires deep_merge-
3618
+ // append). PLAN-079 + PLAN-080: predicates inside exportJsonKey
3619
+ // both ROUTE cards to this section (when no explicit `==== table-
3620
+ // header ====` qualifier) and SELECT the rule body once routed.
3621
+ // First rule = first card with any `[#]` → `columns`; second rule
3622
+ // = subsequent cards in this section → `data` (per-row append).
3623
+ jsonKey: "table.columns",
3624
+ exportJsonKey: [
3625
+ {
3626
+ predicates: [{ "@cardIndex": 0 }, { "@variantHasTag": "#" }],
3627
+ rule: { "@bit": { table: { columns: "$" } } }
3628
+ },
3629
+ { "@cardIndex=0": { "@bit": { table: { columns: "$" } } } },
3630
+ { "@bit": { table: { data: ["$"] } } }
3631
+ ]
3632
+ },
3633
+ "table-body": {
3634
+ // Batched emission: `$` is the entire 2D array of body card rows.
3635
+ // This is the default section (no qualifier → cards land here).
3636
+ jsonKey: "table.data",
3637
+ exportJsonKey: { "@bit": { table: { data: "$" } } },
3638
+ isDefault: true
3639
+ },
3640
+ "table-footer": {
3641
+ // Batched emission: `$` is the entire 2D array of footer rows.
3642
+ jsonKey: "table.data",
3643
+ exportJsonKey: { "@bit": { table: { data: "$" } } }
3644
+ }
3645
+ },
2837
3646
  sides: [
2838
3647
  {
2839
3648
  name: "cell",
@@ -2849,6 +3658,7 @@ var CARDSETS = {
2849
3658
  {
2850
3659
  key: ConfigKey.tag_title,
2851
3660
  jsonKey: "^table.columns[]",
3661
+ exportJsonKey: { "@bit": { table: { columns: ["$"] } } },
2852
3662
  description: "Title of the table."
2853
3663
  },
2854
3664
  {
@@ -2873,6 +3683,8 @@ var CARDSETS = {
2873
3683
  },
2874
3684
  {
2875
3685
  key: ConfigKey.property_tableColWidth,
3686
+ jsonKey: "^table.columnWidths[{s}]",
3687
+ exportJsonKey: { "@bit": { table: { columnWidths: { $s: "$" } } } },
2876
3688
  description: "Width for the column.",
2877
3689
  format: TagFormat.number
2878
3690
  }
@@ -2889,18 +3701,49 @@ var CARDSETS = {
2889
3701
  [CardSetConfigKey.tableExtended]: {
2890
3702
  jsonKey: null,
2891
3703
  sections: {
2892
- "table-header": { jsonKey: "table.header.rows", sideJsonKey: "cells[{s}]|set(title=true)" },
2893
- "table-body": { jsonKey: "table.body.rows", isDefault: true },
2894
- "table-footer": { jsonKey: "table.footer.rows", sideJsonKey: "cells[{s}]|set(title=true)" }
3704
+ "table-header": {
3705
+ jsonKey: "table.header.rows",
3706
+ // PLAN-080: section dispatch via predicates inside exportJsonKey.
3707
+ // Cards with no explicit `==== table-header ====` qualifier
3708
+ // route here if FIRST CARD contains any `[#]` tag in any
3709
+ // variant. Explicit-qualifier cards fall through to the
3710
+ // unconditional fallback rule. Both rules use the per-card
3711
+ // append shape `rows: ['$']` — the runtime triggers per-card
3712
+ // emission whenever the pattern carries predicates, and each
3713
+ // fire appends a single-element array (multi-fire deep-merge
3714
+ // concatenates).
3715
+ exportJsonKey: [
3716
+ {
3717
+ predicates: [{ "@cardIndex": 0 }, { "@variantHasTag": "#" }],
3718
+ rule: { table: { header: { rows: ["$"] } } }
3719
+ },
3720
+ { table: { header: { rows: ["$"] } } }
3721
+ ],
3722
+ sideJsonKey: "cells[{s}]|set(title=true)",
3723
+ sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } }
3724
+ },
3725
+ "table-body": {
3726
+ jsonKey: "table.body.rows",
3727
+ exportJsonKey: { table: { body: { rows: "$" } } },
3728
+ isDefault: true
3729
+ },
3730
+ "table-footer": {
3731
+ jsonKey: "table.footer.rows",
3732
+ exportJsonKey: { table: { footer: { rows: "$" } } },
3733
+ sideJsonKey: "cells[{s}]|set(title=true)",
3734
+ sideExportJsonKey: { cells: { $s: { title: true, $: "$" } } }
3735
+ }
2895
3736
  },
2896
3737
  sides: [
2897
3738
  {
2898
3739
  name: "cell",
2899
3740
  repeat: true,
2900
3741
  jsonKey: "cells[{s}]",
3742
+ exportJsonKey: { cells: { $s: "$" } },
2901
3743
  variants: [
2902
3744
  {
2903
3745
  jsonKey: "content",
3746
+ exportJsonKey: { content: "$" },
2904
3747
  tags: [
2905
3748
  {
2906
3749
  key: ConfigKey.group_standardItemLeadInstructionHint,
@@ -2908,35 +3751,47 @@ var CARDSETS = {
2908
3751
  },
2909
3752
  {
2910
3753
  key: ConfigKey.tag_title,
2911
- jsonKey: ".",
2912
- description: "Title of the table cell."
3754
+ // PLAN-080: the `[#]` tag writes its text to the cell's
3755
+ // `content` field. When the card is in the `table-header`
3756
+ // section, the section's sideExportJsonKey wraps each
3757
+ // cell with `title: true` automatically.
3758
+ jsonKey: "content",
3759
+ exportJsonKey: { content: "$" },
3760
+ description: "Title text of the table cell (header rows)."
2913
3761
  },
2914
3762
  {
2915
3763
  key: ConfigKey.property_tableCellType,
2916
3764
  jsonKey: "title|bool(th)",
3765
+ exportJsonKey: [{ "@value=th": { title: true } }],
2917
3766
  description: "Table cell type (th/td).",
2918
3767
  format: TagFormat.plainText
2919
3768
  },
2920
3769
  {
2921
3770
  key: ConfigKey.property_tableRowSpan,
2922
3771
  jsonKey: "rowspan",
3772
+ exportJsonKey: { rowspan: "$" },
2923
3773
  description: "Number of rows the cell spans.",
2924
3774
  format: TagFormat.number
2925
3775
  },
2926
3776
  {
2927
3777
  key: ConfigKey.property_tableColSpan,
2928
3778
  jsonKey: "colspan",
3779
+ exportJsonKey: { colspan: "$" },
2929
3780
  description: "Number of columns the cell spans.",
2930
3781
  format: TagFormat.number
2931
3782
  },
2932
3783
  {
2933
3784
  key: ConfigKey.property_tableScope,
2934
3785
  jsonKey: "scope",
3786
+ exportJsonKey: { scope: "$" },
2935
3787
  description: "Scope attribute for header cells.",
2936
3788
  format: TagFormat.plainText
2937
3789
  },
2938
3790
  {
2939
3791
  key: ConfigKey.property_tableColWidth,
3792
+ // PLAN-080: write to the cell's `colwidth` field.
3793
+ jsonKey: "colwidth",
3794
+ exportJsonKey: { colwidth: "$" },
2940
3795
  description: "Width for the column.",
2941
3796
  format: TagFormat.number
2942
3797
  }
@@ -2952,6 +3807,7 @@ var CARDSETS = {
2952
3807
  //
2953
3808
  [CardSetConfigKey.pronunciationTable]: {
2954
3809
  jsonKey: "pronunciationTable.data",
3810
+ exportJsonKey: { pronunciationTable: { data: "$" } },
2955
3811
  sides: [
2956
3812
  {
2957
3813
  name: "cell",
@@ -2959,19 +3815,22 @@ var CARDSETS = {
2959
3815
  variants: [
2960
3816
  {
2961
3817
  jsonKey: "body",
3818
+ exportJsonKey: { body: "$" },
2962
3819
  tags: [
2963
3820
  {
2964
3821
  key: ConfigKey.group_standardItemLeadInstructionHint,
2965
3822
  description: "Standard tags for lead, instruction, and hint."
2966
3823
  },
2967
3824
  {
3825
+ exportJsonKey: { title: "$" },
2968
3826
  key: ConfigKey.tag_title,
2969
3827
  description: "Title of the pronunciation table."
2970
3828
  },
2971
3829
  {
2972
3830
  key: ConfigKey.resource_audio,
2973
3831
  description: "Audio resource for the pronunciation table.",
2974
- jsonKey: "audio"
3832
+ jsonKey: "audio",
3833
+ exportJsonKey: { audio: { type: "audio", audio: { src: "$" } } }
2975
3834
  }
2976
3835
  ],
2977
3836
  repeatCount: Count.infinity
@@ -2985,12 +3844,15 @@ var CARDSETS = {
2985
3844
  //
2986
3845
  [CardSetConfigKey.botActionResponses]: {
2987
3846
  jsonKey: "responses",
3847
+ exportJsonKey: { responses: "$" },
2988
3848
  sides: [
2989
3849
  {
2990
3850
  name: "response",
2991
3851
  variants: [
2992
3852
  {
2993
3853
  jsonKey: "feedback",
3854
+ exportJsonKey: { feedback: "$" },
3855
+ format: TextFormat2.plainText,
2994
3856
  tags: [
2995
3857
  {
2996
3858
  key: ConfigKey.property_reaction,
@@ -2998,18 +3860,58 @@ var CARDSETS = {
2998
3860
  format: TagFormat.plainText
2999
3861
  },
3000
3862
  {
3001
- key: ConfigKey.group_standardItemLead,
3002
- description: "Item, lead, page number, and margin number for the bot action response."
3863
+ key: ConfigKey.tag_item,
3864
+ jsonKey: "item",
3865
+ exportJsonKey: { item: "$" },
3866
+ format: TagFormat.plainText,
3867
+ description: "The item for the bit",
3868
+ chain: [
3869
+ {
3870
+ key: ConfigKey.tag_item,
3871
+ jsonKey: "lead",
3872
+ exportJsonKey: { lead: "$" },
3873
+ description: "The lead for the bit",
3874
+ maxCount: 1,
3875
+ chain: [
3876
+ {
3877
+ key: ConfigKey.tag_item,
3878
+ jsonKey: "pageNumber",
3879
+ exportJsonKey: { pageNumber: "$" },
3880
+ description: "The page number for the bit",
3881
+ maxCount: 1,
3882
+ chain: [
3883
+ {
3884
+ key: ConfigKey.tag_item,
3885
+ jsonKey: "marginNumber",
3886
+ exportJsonKey: { marginNumber: "$" },
3887
+ description: "The margin number for the bit",
3888
+ maxCount: 1
3889
+ }
3890
+ ]
3891
+ }
3892
+ ]
3893
+ }
3894
+ ]
3003
3895
  },
3004
3896
  {
3005
3897
  key: ConfigKey.tag_instruction,
3006
3898
  description: "The response label for the bot action.",
3007
- jsonKey: "response"
3899
+ jsonKey: "response",
3900
+ exportJsonKey: { response: "$" },
3901
+ format: TagFormat.plainText
3008
3902
  },
3009
3903
  {
3904
+ // ALIGN-EXAMPLE-CASCADE §3.12: `bot-action-responses` is
3905
+ // NOT in BPG's `pushExampleDownTree` dispatch — no bit-
3906
+ // header cascade.
3010
3907
  key: ConfigKey.property_example,
3011
- description: "Example text for the bot action response.",
3012
- format: TagFormat.plainText
3908
+ exportJsonKey: [
3909
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
3910
+ { isExample: true, example: "$", "@bit": { isExample: true } }
3911
+ ],
3912
+ description: "Example text for the bot action response (entry-local only; no bit-header cascade).",
3913
+ format: TagFormat.plainText,
3914
+ nullable: true
3013
3915
  }
3014
3916
  ]
3015
3917
  }
@@ -3022,12 +3924,14 @@ var CARDSETS = {
3022
3924
  //
3023
3925
  [CardSetConfigKey.clozeList]: {
3024
3926
  jsonKey: "listItems",
3927
+ exportJsonKey: { listItems: "$" },
3025
3928
  sides: [
3026
3929
  {
3027
3930
  name: "content",
3028
3931
  variants: [
3029
3932
  {
3030
3933
  jsonKey: "body",
3934
+ exportJsonKey: { body: "$" },
3031
3935
  tags: [
3032
3936
  {
3033
3937
  key: ConfigKey.group_standardTags,
@@ -3048,18 +3952,21 @@ var CARDSETS = {
3048
3952
  //
3049
3953
  [CardSetConfigKey.exampleBitList]: {
3050
3954
  jsonKey: "listItems",
3955
+ exportJsonKey: { listItems: "$" },
3051
3956
  sides: [
3052
3957
  {
3053
3958
  name: "item",
3054
3959
  variants: [
3055
3960
  {
3056
3961
  jsonKey: "body",
3962
+ exportJsonKey: { body: "$" },
3057
3963
  tags: [
3058
3964
  {
3059
3965
  key: ConfigKey.group_standardTags,
3060
3966
  description: "Standard tags for example bits."
3061
3967
  },
3062
3968
  {
3969
+ exportJsonKey: { title: "$" },
3063
3970
  key: ConfigKey.tag_title,
3064
3971
  description: "Title of the example bit."
3065
3972
  }
@@ -3070,18 +3977,53 @@ var CARDSETS = {
3070
3977
  ]
3071
3978
  },
3072
3979
  //
3980
+ // page-footer-sections — mirrors example-bit-list but emits cards under
3981
+ // `sections` instead of `listItems`. BPG's JsonGenerator special-cases
3982
+ // BitType.pageFooter in code; this dedicated cardSet expresses the same
3983
+ // routing as pure config so the Rust serializer can pick it up.
3984
+ //
3985
+ [CardSetConfigKey.pageFooterSections]: {
3986
+ jsonKey: "sections",
3987
+ exportJsonKey: { sections: "$" },
3988
+ sides: [
3989
+ {
3990
+ name: "item",
3991
+ variants: [
3992
+ {
3993
+ jsonKey: "body",
3994
+ exportJsonKey: { body: "$" },
3995
+ tags: [
3996
+ {
3997
+ key: ConfigKey.group_standardTags,
3998
+ description: "Standard tags for page-footer section bits."
3999
+ },
4000
+ {
4001
+ exportJsonKey: { title: "$" },
4002
+ key: ConfigKey.tag_title,
4003
+ description: "Title of the page-footer section."
4004
+ }
4005
+ ]
4006
+ }
4007
+ ]
4008
+ }
4009
+ ]
4010
+ },
4011
+ //
3073
4012
  // ingredients
3074
4013
  //
3075
4014
  [CardSetConfigKey.ingredients]: {
3076
4015
  jsonKey: "ingredients",
4016
+ exportJsonKey: { ingredients: "$" },
3077
4017
  sides: [
3078
4018
  {
3079
4019
  name: "ingredient",
3080
4020
  variants: [
3081
4021
  {
3082
4022
  jsonKey: "ingredient",
4023
+ exportJsonKey: { ingredient: "$" },
3083
4024
  tags: [
3084
4025
  {
4026
+ exportJsonKey: { title: "$" },
3085
4027
  key: ConfigKey.tag_title,
3086
4028
  description: "Title of the ingredient."
3087
4029
  },
@@ -3089,6 +4031,7 @@ var CARDSETS = {
3089
4031
  key: ConfigKey.tag_true,
3090
4032
  description: "Checked state for the ingredient.",
3091
4033
  jsonKey: "|set(checked=true)",
4034
+ exportJsonKey: { checked: true, $: "$" },
3092
4035
  maxCount: 1
3093
4036
  // nullable: true,
3094
4037
  },
@@ -3096,6 +4039,7 @@ var CARDSETS = {
3096
4039
  key: ConfigKey.tag_false,
3097
4040
  description: "Unchecked state for the ingredient.",
3098
4041
  jsonKey: "|set(checked=false)",
4042
+ exportJsonKey: { checked: false, $: "$" },
3099
4043
  maxCount: 1
3100
4044
  // nullable: true,
3101
4045
  },
@@ -3107,9 +4051,11 @@ var CARDSETS = {
3107
4051
  key: ConfigKey.tag_instruction,
3108
4052
  description: "The quantity of the ingredient.",
3109
4053
  jsonKey: "quantity",
4054
+ exportJsonKey: { quantity: "$" },
3110
4055
  format: TagFormat.number
3111
4056
  },
3112
4057
  {
4058
+ exportJsonKey: { hint: "$" },
3113
4059
  key: ConfigKey.tag_hint,
3114
4060
  description: "The hint for the ingredient."
3115
4061
  },
@@ -3145,6 +4091,7 @@ var CARDSETS = {
3145
4091
  //
3146
4092
  [CardSetConfigKey.bookReferenceList]: {
3147
4093
  jsonKey: "bookReferences",
4094
+ exportJsonKey: { bookReferences: "$" },
3148
4095
  sides: [
3149
4096
  {
3150
4097
  name: "reference",
@@ -3204,6 +4151,11 @@ var GROUPS = {
3204
4151
  format: TagFormat.plainText,
3205
4152
  maxCount: Count.infinity
3206
4153
  },
4154
+ {
4155
+ key: ConfigKey.property_gUri,
4156
+ description: "Global URI for the bit",
4157
+ format: TagFormat.plainText
4158
+ },
3207
4159
  {
3208
4160
  key: ConfigKey.property_customerId,
3209
4161
  description: "The customer-specific identifier for the bit",
@@ -3387,12 +4339,18 @@ var GROUPS = {
3387
4339
  {
3388
4340
  key: ConfigKey.property_groupTag,
3389
4341
  jsonKey: "groupTag.name",
4342
+ // PLAN-063: starter-array shape with `@id` declares entity-merge
4343
+ // semantics — repeated `[@groupTag: name]` occurrences with the
4344
+ // same value fold into one entry; chained `@tag` children's
4345
+ // arrays merge as sets.
4346
+ exportJsonKey: { groupTag: [{ "@id": "name", name: "$" }] },
3390
4347
  description: "The group tag(s) for the bit",
3391
4348
  format: TagFormat.plainText,
3392
4349
  maxCount: Count.infinity,
3393
4350
  chain: [
3394
4351
  {
3395
4352
  key: ConfigKey.property_tag,
4353
+ exportJsonKey: { tags: ["$"] },
3396
4354
  description: "The tag(s) for the group",
3397
4355
  format: TagFormat.plainText,
3398
4356
  maxCount: Count.infinity
@@ -3423,6 +4381,12 @@ var GROUPS = {
3423
4381
  format: TagFormat.plainText,
3424
4382
  maxCount: Count.infinity
3425
4383
  },
4384
+ {
4385
+ key: ConfigKey.property_sourceBB,
4386
+ description: "The source bounding box(es) for the bit (x, y, x1, y1)",
4387
+ format: TagFormat.coordinates,
4388
+ maxCount: Count.infinity
4389
+ },
3426
4390
  {
3427
4391
  key: ConfigKey.property_levelCEFRp,
3428
4392
  description: "The CEFRp level for the bit",
@@ -3460,6 +4424,7 @@ var GROUPS = {
3460
4424
  maxCount: Count.infinity
3461
4425
  },
3462
4426
  {
4427
+ exportJsonKey: { anchor: "$" },
3463
4428
  key: ConfigKey.tag_anchor,
3464
4429
  name: "Anchor",
3465
4430
  format: TagFormat.plainText,
@@ -3497,23 +4462,27 @@ var GROUPS = {
3497
4462
  {
3498
4463
  key: ConfigKey.tag_item,
3499
4464
  jsonKey: "item",
4465
+ exportJsonKey: { item: "$" },
3500
4466
  description: "The item for the bit",
3501
4467
  chain: [
3502
4468
  {
3503
4469
  key: ConfigKey.tag_item,
3504
4470
  jsonKey: "lead",
4471
+ exportJsonKey: { lead: "$" },
3505
4472
  description: "The lead for the bit",
3506
4473
  maxCount: 1,
3507
4474
  chain: [
3508
4475
  {
3509
4476
  key: ConfigKey.tag_item,
3510
4477
  jsonKey: "pageNumber",
4478
+ exportJsonKey: { pageNumber: "$" },
3511
4479
  description: "The page number for the bit",
3512
4480
  maxCount: 1,
3513
4481
  chain: [
3514
4482
  {
3515
4483
  key: ConfigKey.tag_item,
3516
4484
  jsonKey: "marginNumber",
4485
+ exportJsonKey: { marginNumber: "$" },
3517
4486
  description: "The margin number for the bit",
3518
4487
  maxCount: 1
3519
4488
  }
@@ -3534,11 +4503,31 @@ var GROUPS = {
3534
4503
  description: "Item, lead, page number, and margin number tags"
3535
4504
  },
3536
4505
  {
4506
+ exportJsonKey: { instruction: "$" },
4507
+ key: ConfigKey.tag_instruction,
4508
+ name: "Instruction",
4509
+ description: "The instruction for the bit"
4510
+ },
4511
+ {
4512
+ exportJsonKey: { hint: "$" },
4513
+ key: ConfigKey.tag_hint,
4514
+ name: "Hint",
4515
+ description: "The hint for the bit"
4516
+ }
4517
+ ]
4518
+ },
4519
+ [ConfigKey.group_standardInstructionHint]: {
4520
+ type: GroupConfigType.standard,
4521
+ description: "Standard group for instruction and hint tags only (no item/lead/pageNumber/marginNumber chain). Used by sides where item/lead are owned by another side (e.g. matchPairs value-side, which BPG strips item/lead from at sideIdx > 0).",
4522
+ tags: [
4523
+ {
4524
+ exportJsonKey: { instruction: "$" },
3537
4525
  key: ConfigKey.tag_instruction,
3538
4526
  name: "Instruction",
3539
4527
  description: "The instruction for the bit"
3540
4528
  },
3541
4529
  {
4530
+ exportJsonKey: { hint: "$" },
3542
4531
  key: ConfigKey.tag_hint,
3543
4532
  name: "Hint",
3544
4533
  description: "The hint for the bit"
@@ -3558,9 +4547,45 @@ var GROUPS = {
3558
4547
  description: "Item, lead, page number, margin number, instruction and hint tags"
3559
4548
  },
3560
4549
  {
4550
+ // Bit-level example tag (default — non-`isTopLevelExample` bits):
4551
+ // - bare `[@example]`: consume only (records cascade marker, emits
4552
+ // nothing at bit-level — OLD parser scrubs bit-level isExample
4553
+ // for bits NOT in its `isTopLevelExample` allow-list).
4554
+ // - synthesized (descendant cascade): still emits `isExample: true`
4555
+ // at the descendant scope.
4556
+ // - valued `[@example:text]`: emit the example value only; bit-level
4557
+ // `isExample` is left to bubble from descendants.
4558
+ // Bits in the OLD parser's allow-list (flashcard, definitionList,
4559
+ // sequence, multipleChoice, trueFalse, cloze*, mark, interview,
4560
+ // matchMatrix, match, essay, etc.) override this entry with the
4561
+ // legacy emit-isExample shape.
3561
4562
  key: ConfigKey.property_example,
4563
+ exportJsonKey: [{ "@keyonly": {} }, { "@absent": { isExample: true } }, { example: "$" }],
3562
4564
  description: "The example(s) for the bit",
3563
- format: TagFormat.plainText
4565
+ format: TagFormat.bitmarkText,
4566
+ nullable: true
4567
+ }
4568
+ ]
4569
+ },
4570
+ [ConfigKey.group_standardTagsNoItemLead]: {
4571
+ type: GroupConfigType.standard,
4572
+ description: "Standard tags mirror of group_standardTags but excluding the item/lead/pageNumber/marginNumber chain. Used by sides where item/lead are owned by another side (e.g. matchPairs value-side \u2014 BPG `parseMatchPairs` strips tags.item / tags.lead for sideIdx > 0).",
4573
+ tags: [
4574
+ {
4575
+ key: ConfigKey.group_standardAllBits,
4576
+ description: "All standard tags which apply to all bits"
4577
+ },
4578
+ {
4579
+ key: ConfigKey.group_standardInstructionHint,
4580
+ description: "Instruction and hint tags only (no item/lead chain)."
4581
+ },
4582
+ {
4583
+ // Mirror of group_standardTags property_example entry.
4584
+ key: ConfigKey.property_example,
4585
+ exportJsonKey: [{ "@keyonly": {} }, { "@absent": { isExample: true } }, { example: "$" }],
4586
+ description: "The example(s) for the bit",
4587
+ format: TagFormat.bitmarkText,
4588
+ nullable: true
3564
4589
  }
3565
4590
  ]
3566
4591
  },
@@ -3571,6 +4596,7 @@ var GROUPS = {
3571
4596
  {
3572
4597
  key: ConfigKey.property_imageSource,
3573
4598
  jsonKey: "imageSource.url",
4599
+ exportJsonKey: { imageSource: { url: "$" } },
3574
4600
  description: "The source of an image",
3575
4601
  format: TagFormat.plainText,
3576
4602
  chain: [
@@ -3605,6 +4631,7 @@ var GROUPS = {
3605
4631
  {
3606
4632
  key: ConfigKey.property_technicalTerm,
3607
4633
  jsonKey: "technicalTerm.technicalTerm",
4634
+ exportJsonKey: { technicalTerm: { technicalTerm: "$" } },
3608
4635
  description: "The technical term",
3609
4636
  format: TagFormat.plainText,
3610
4637
  chain: [
@@ -3623,7 +4650,8 @@ var GROUPS = {
3623
4650
  tags: [
3624
4651
  {
3625
4652
  key: ConfigKey.property_person,
3626
- jsonKey: "partner.name",
4653
+ jsonKey: "person.name",
4654
+ exportJsonKey: { person: { name: "$" } },
3627
4655
  description: "A person",
3628
4656
  format: TagFormat.plainText,
3629
4657
  chain: [
@@ -3635,6 +4663,7 @@ var GROUPS = {
3635
4663
  {
3636
4664
  key: ConfigKey.group_resourceImage,
3637
4665
  jsonKey: "avatarImage|resource(type=image, key=image)",
4666
+ exportJsonKey: { avatarImage: { type: "image", image: { src: "$" } } },
3638
4667
  description: "The image of the person"
3639
4668
  }
3640
4669
  ]
@@ -3642,7 +4671,8 @@ var GROUPS = {
3642
4671
  {
3643
4672
  // Deprecated (parter renamed to person)
3644
4673
  key: ConfigKey.property_partner,
3645
- jsonKey: "partner.name",
4674
+ jsonKey: "person.name",
4675
+ exportJsonKey: { person: { name: "$" } },
3646
4676
  description: "A partner",
3647
4677
  format: TagFormat.plainText,
3648
4678
  chain: [
@@ -3654,6 +4684,7 @@ var GROUPS = {
3654
4684
  {
3655
4685
  key: ConfigKey.group_resourceImage,
3656
4686
  jsonKey: "avatarImage|resource(type=image, key=image)",
4687
+ exportJsonKey: { avatarImage: { type: "image", image: { src: "$" } } },
3657
4688
  description: "The image of the partner"
3658
4689
  }
3659
4690
  ]
@@ -3665,29 +4696,66 @@ var GROUPS = {
3665
4696
  description: "Gap chain",
3666
4697
  tags: [
3667
4698
  {
4699
+ exportJsonKey: {
4700
+ "@body-inline": { type: "gap", attrs: { solutions: ["$"] } }
4701
+ },
3668
4702
  key: ConfigKey.tag_gap,
3669
4703
  description: "The value of a gap in the content",
3670
4704
  maxCount: Count.infinity,
4705
+ format: TagFormat.plainText,
3671
4706
  chain: [
3672
4707
  {
3673
4708
  key: ConfigKey.tag_gap,
3674
4709
  jsonKey: "solutions[]",
4710
+ exportJsonKey: { solutions: ["$"] },
3675
4711
  description: "Alternative values for the gaps in the content",
3676
- maxCount: Count.infinity
4712
+ maxCount: Count.infinity,
4713
+ format: TagFormat.plainText
3677
4714
  },
3678
4715
  {
3679
4716
  key: ConfigKey.group_standardItemLeadInstructionHint,
3680
4717
  description: "Item, lead, page number, margin number, instruction and hint tags"
3681
4718
  },
3682
4719
  {
4720
+ // BPG ExampleTagContentProcessor.ts:91-95 reads
4721
+ // `__solutionsAst[length - 1]` — the solution closest to the
4722
+ // `[@example]` tag at parse time. Our chain accumulator is built
4723
+ // incrementally (scope.rs `parent.merge` after each tag), so at
4724
+ // the moment `[@example]` fires, `solutions` only contains the
4725
+ // preceding `[_x]` entries — `[-1]` resolves to the closest one.
4726
+ // E.g. `[_cloze][_gap][@example]` → solutions=["cloze","gap"],
4727
+ // `[-1]`="gap"; `[_solution][@example][_problem]` →
4728
+ // solutions=["solution"], `[-1]`="solution".
3683
4729
  key: ConfigKey.property_example,
3684
4730
  jsonKey: "example",
4731
+ exportJsonKey: [
4732
+ {
4733
+ "@keyonly": {
4734
+ isExample: true,
4735
+ example: "$parent.solutions[-1]",
4736
+ "@bit": { isExample: true }
4737
+ }
4738
+ },
4739
+ {
4740
+ "@absent": {
4741
+ isExample: true,
4742
+ example: "$parent.solutions[-1]",
4743
+ "@bit": { isExample: true }
4744
+ }
4745
+ },
4746
+ { isExample: true, example: "$", "@bit": { isExample: true } }
4747
+ ],
3685
4748
  description: "An example for the gap",
3686
- format: TagFormat.plainText
4749
+ format: TagFormat.bitmarkText,
4750
+ nullable: true
3687
4751
  },
3688
4752
  {
3689
4753
  key: ConfigKey.property_isCaseSensitive,
3690
4754
  jsonKey: "isCaseSensitive",
4755
+ exportJsonKey: [
4756
+ { "@absent": { isCaseSensitive: "$ancestor" } },
4757
+ { isCaseSensitive: "$" }
4758
+ ],
3691
4759
  description: "If true, the gap text is case sensitive",
3692
4760
  format: TagFormat.boolean,
3693
4761
  defaultValue: "true"
@@ -3701,60 +4769,587 @@ var GROUPS = {
3701
4769
  description: "True/False chain",
3702
4770
  tags: [
3703
4771
  {
4772
+ exportJsonKey: { choices: [{ choice: "$", isCorrect: true }] },
3704
4773
  key: ConfigKey.tag_true,
3705
4774
  description: "True value for a true/false statement/question",
3706
4775
  maxCount: Count.infinity,
4776
+ format: TagFormat.plainText,
3707
4777
  chain: [
3708
4778
  {
3709
4779
  key: ConfigKey.tag_true,
3710
4780
  jsonKey: "choices[]|set(isCorrect=true)",
4781
+ exportJsonKey: { choices: [{ choice: "$", isCorrect: true }] },
3711
4782
  description: "True values for a true/false statement/question",
3712
- maxCount: Count.infinity
4783
+ maxCount: Count.infinity,
4784
+ format: TagFormat.plainText
3713
4785
  },
3714
4786
  {
3715
4787
  key: ConfigKey.tag_false,
3716
4788
  jsonKey: "choices[]|set(isCorrect=false)",
4789
+ exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
3717
4790
  description: "False values for a true/false statement/question",
3718
- maxCount: Count.infinity
4791
+ maxCount: Count.infinity,
4792
+ format: TagFormat.plainText
3719
4793
  },
3720
4794
  {
3721
4795
  key: ConfigKey.group_standardItemLeadInstructionHint,
3722
4796
  description: "Item, lead, page number, margin number, instruction and hint tags"
3723
4797
  },
3724
4798
  {
4799
+ // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.2: cascade-fired
4800
+ // `@example` from the bit header marks EVERY choice with
4801
+ // `isExample: true` (matches BPG output), but only the
4802
+ // FIRST correct choice gets `example: true` (BPG
4803
+ // `fillBooleanExample(..., firstCorrectOnly=true)`).
4804
+ //
4805
+ // Two `@absent` rules, picked first-match-wins:
4806
+ // 1. Verbose form gated on `@parent.isCorrect=true` with
4807
+ // `maxEmits:1` — fires once for the first correct.
4808
+ // 2. Plain `@absent` — fires on every remaining entry
4809
+ // (subsequent corrects and all incorrects), emitting
4810
+ // `isExample` only.
4811
+ // The `serialize_cards` cascade-counter scope holds the cap
4812
+ // across the whole cardset.
4813
+ // @card scope-shift writes choice-container isExample (e.g.
4814
+ // `quizzes[i].isExample`) — was previously provided by the
4815
+ // universal `bubble_is_example` post-walk, which was removed
4816
+ // in favour of explicit per-tag config. SYNTAX.md §8.
3725
4817
  key: ConfigKey.property_example,
3726
4818
  jsonKey: "example",
4819
+ exportJsonKey: [
4820
+ {
4821
+ "@keyonly": {
4822
+ isExample: true,
4823
+ example: "$parent.isCorrect",
4824
+ "@bit": { isExample: true },
4825
+ "@card": { isExample: true }
4826
+ }
4827
+ },
4828
+ {
4829
+ predicates: ["@absent", { "@parent.isCorrect": true }],
4830
+ maxEmits: 1,
4831
+ rule: {
4832
+ isExample: true,
4833
+ example: true,
4834
+ "@bit": { isExample: true },
4835
+ "@card": { isExample: true }
4836
+ }
4837
+ },
4838
+ {
4839
+ "@absent": {
4840
+ isExample: true,
4841
+ "@bit": { isExample: true },
4842
+ "@card": { isExample: true }
4843
+ }
4844
+ },
4845
+ {
4846
+ isExample: true,
4847
+ example: "$",
4848
+ "@bit": { isExample: true },
4849
+ "@card": { isExample: true }
4850
+ }
4851
+ ],
3727
4852
  description: "An example for the true/false statement/question",
3728
- format: TagFormat.plainText
4853
+ format: TagFormat.boolean,
4854
+ nullable: true
3729
4855
  }
3730
4856
  ]
3731
4857
  },
3732
4858
  {
4859
+ exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
3733
4860
  key: ConfigKey.tag_false,
3734
4861
  description: "False value for a true/false statement/question",
3735
4862
  maxCount: Count.infinity,
4863
+ format: TagFormat.plainText,
3736
4864
  chain: [
3737
4865
  {
3738
4866
  key: ConfigKey.tag_true,
3739
4867
  jsonKey: "choices[]|set(isCorrect=true)",
4868
+ exportJsonKey: { choices: [{ choice: "$", isCorrect: true }] },
3740
4869
  description: "True values for a true/false statement/question",
3741
- maxCount: Count.infinity
4870
+ maxCount: Count.infinity,
4871
+ format: TagFormat.plainText
3742
4872
  },
3743
4873
  {
3744
4874
  key: ConfigKey.tag_false,
3745
4875
  jsonKey: "choices[]|set(isCorrect=false)",
4876
+ exportJsonKey: { choices: [{ choice: "$", isCorrect: false }] },
3746
4877
  description: "False values for a true/false statement/question",
3747
- maxCount: Count.infinity
4878
+ maxCount: Count.infinity,
4879
+ format: TagFormat.plainText
4880
+ },
4881
+ {
4882
+ key: ConfigKey.group_standardItemLeadInstructionHint,
4883
+ description: "Item, lead, page number, margin number, instruction and hint tags"
4884
+ },
4885
+ {
4886
+ // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.2: cascade-fired
4887
+ // `@example` on a `-` (incorrect choice) emits
4888
+ // `isExample: true` only — no `example` (BPG fixtures show
4889
+ // `isExample` on every choice, but `example` only on the
4890
+ // first correct via firstCorrectOnly cascade).
4891
+ // @card scope-shift writes choice-container isExample.
4892
+ key: ConfigKey.property_example,
4893
+ jsonKey: "example",
4894
+ exportJsonKey: [
4895
+ {
4896
+ "@keyonly": {
4897
+ isExample: true,
4898
+ example: "$parent.isCorrect",
4899
+ "@bit": { isExample: true },
4900
+ "@card": { isExample: true }
4901
+ }
4902
+ },
4903
+ {
4904
+ "@absent": {
4905
+ isExample: true,
4906
+ "@bit": { isExample: true },
4907
+ "@card": { isExample: true }
4908
+ }
4909
+ },
4910
+ {
4911
+ isExample: true,
4912
+ example: "$",
4913
+ "@bit": { isExample: true },
4914
+ "@card": { isExample: true }
4915
+ }
4916
+ ],
4917
+ description: "An example for the true/false statement/question (incorrect entry \u2014 isExample only on cascade)",
4918
+ format: TagFormat.boolean,
4919
+ nullable: true
4920
+ }
4921
+ ]
4922
+ }
4923
+ ]
4924
+ },
4925
+ [ConfigKey.group_trueFalseResponses]: {
4926
+ type: GroupConfigType.standard,
4927
+ description: "True/False Responses chain",
4928
+ tags: [
4929
+ {
4930
+ exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
4931
+ key: ConfigKey.tag_true,
4932
+ description: "True value for a true/false statement/question",
4933
+ maxCount: Count.infinity,
4934
+ format: TagFormat.plainText,
4935
+ chain: [
4936
+ {
4937
+ key: ConfigKey.tag_true,
4938
+ jsonKey: "responses[]|set(isCorrect=true)",
4939
+ exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
4940
+ description: "True values for a true/false statement/question",
4941
+ maxCount: Count.infinity,
4942
+ format: TagFormat.plainText
4943
+ },
4944
+ {
4945
+ key: ConfigKey.tag_false,
4946
+ jsonKey: "responses[]|set(isCorrect=false)",
4947
+ exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
4948
+ description: "False values for a true/false statement/question",
4949
+ maxCount: Count.infinity,
4950
+ format: TagFormat.plainText
4951
+ },
4952
+ {
4953
+ key: ConfigKey.group_standardItemLeadInstructionHint,
4954
+ description: "Item, lead, page number, margin number, instruction and hint tags"
4955
+ },
4956
+ {
4957
+ // @card scope-shift writes response-container isExample.
4958
+ key: ConfigKey.property_example,
4959
+ jsonKey: "example",
4960
+ exportJsonKey: [
4961
+ {
4962
+ "@keyonly": {
4963
+ isExample: true,
4964
+ example: "$parent.isCorrect",
4965
+ "@bit": { isExample: true },
4966
+ "@card": { isExample: true }
4967
+ }
4968
+ },
4969
+ {
4970
+ "@absent": {
4971
+ isExample: "$parent.isCorrect",
4972
+ example: "$parent.isCorrect",
4973
+ "@bit": { isExample: "$parent.isCorrect" },
4974
+ "@card": { isExample: "$parent.isCorrect" }
4975
+ }
4976
+ },
4977
+ {
4978
+ isExample: true,
4979
+ example: "$",
4980
+ "@bit": { isExample: true },
4981
+ "@card": { isExample: true }
4982
+ }
4983
+ ],
4984
+ description: "An example for the true/false statement/question",
4985
+ format: TagFormat.boolean,
4986
+ nullable: true
4987
+ }
4988
+ ]
4989
+ },
4990
+ {
4991
+ exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
4992
+ key: ConfigKey.tag_false,
4993
+ description: "False value for a true/false statement/question",
4994
+ maxCount: Count.infinity,
4995
+ format: TagFormat.plainText,
4996
+ chain: [
4997
+ {
4998
+ key: ConfigKey.tag_true,
4999
+ jsonKey: "responses[]|set(isCorrect=true)",
5000
+ exportJsonKey: { responses: [{ response: "$", isCorrect: true }] },
5001
+ description: "True values for a true/false statement/question",
5002
+ maxCount: Count.infinity,
5003
+ format: TagFormat.plainText
5004
+ },
5005
+ {
5006
+ key: ConfigKey.tag_false,
5007
+ jsonKey: "responses[]|set(isCorrect=false)",
5008
+ exportJsonKey: { responses: [{ response: "$", isCorrect: false }] },
5009
+ description: "False values for a true/false statement/question",
5010
+ maxCount: Count.infinity,
5011
+ format: TagFormat.plainText
3748
5012
  },
3749
5013
  {
3750
5014
  key: ConfigKey.group_standardItemLeadInstructionHint,
3751
5015
  description: "Item, lead, page number, margin number, instruction and hint tags"
3752
5016
  },
3753
5017
  {
5018
+ // @card scope-shift writes response-container isExample.
3754
5019
  key: ConfigKey.property_example,
3755
5020
  jsonKey: "example",
5021
+ exportJsonKey: [
5022
+ {
5023
+ "@keyonly": {
5024
+ isExample: true,
5025
+ example: "$parent.isCorrect",
5026
+ "@bit": { isExample: true },
5027
+ "@card": { isExample: true }
5028
+ }
5029
+ },
5030
+ {
5031
+ "@absent": {
5032
+ isExample: true,
5033
+ example: "$parent.isCorrect",
5034
+ "@bit": { isExample: true },
5035
+ "@card": { isExample: true }
5036
+ }
5037
+ },
5038
+ {
5039
+ isExample: true,
5040
+ example: "$",
5041
+ "@bit": { isExample: true },
5042
+ "@card": { isExample: true }
5043
+ }
5044
+ ],
3756
5045
  description: "An example for the true/false statement/question",
5046
+ format: TagFormat.boolean,
5047
+ nullable: true
5048
+ }
5049
+ ]
5050
+ }
5051
+ ]
5052
+ },
5053
+ [ConfigKey.group_trueFalseInlineSelect]: {
5054
+ type: GroupConfigType.standard,
5055
+ description: "True/False chain composed inline (select body-bit). +/- tags are consume-only at bit level.",
5056
+ tags: [
5057
+ {
5058
+ exportJsonKey: {
5059
+ "@body-inline": {
5060
+ type: "select",
5061
+ attrs: { options: [{ text: "$", isCorrect: true }] }
5062
+ }
5063
+ },
5064
+ key: ConfigKey.tag_true,
5065
+ description: "True value for an inline select body bit",
5066
+ maxCount: Count.infinity,
5067
+ format: TagFormat.plainText,
5068
+ chain: [
5069
+ {
5070
+ key: ConfigKey.tag_true,
5071
+ exportJsonKey: {},
5072
+ description: "True values for an inline select body bit",
5073
+ maxCount: Count.infinity,
5074
+ format: TagFormat.plainText
5075
+ },
5076
+ {
5077
+ key: ConfigKey.tag_false,
5078
+ exportJsonKey: {},
5079
+ description: "False values for an inline select body bit",
5080
+ maxCount: Count.infinity,
5081
+ format: TagFormat.plainText
5082
+ },
5083
+ {
5084
+ key: ConfigKey.group_standardItemLeadInstructionHint,
5085
+ description: "Item, lead, page number, margin number, instruction and hint tags"
5086
+ },
5087
+ {
5088
+ // PLAN-083: cascade-fired `@example` from bit header fires
5089
+ // `isExample:true, example:true` on the FIRST CORRECT option
5090
+ // only (BPG `fillBooleanExample(select.options,
5091
+ // firstCorrectOnly=true)`). Mirrors group_trueFalseInlineHighlight.
5092
+ // Every rule also bubbles `@bit:{isExample:true}` (idempotent
5093
+ // OR — any cascade ⇒ bit marked) and
5094
+ // `@body-inline:{attrs:{isExample:true}}` (node-level marker
5095
+ // on the select node).
5096
+ key: ConfigKey.property_example,
5097
+ jsonKey: "example",
5098
+ exportJsonKey: [
5099
+ {
5100
+ "@keyonly": {
5101
+ isExample: true,
5102
+ example: "$parent.isCorrect",
5103
+ "@bit": { isExample: true },
5104
+ "@body-inline": { attrs: { isExample: true } }
5105
+ }
5106
+ },
5107
+ {
5108
+ predicates: ["@absent", { "@parent.isCorrect": true }],
5109
+ maxEmits: 1,
5110
+ rule: {
5111
+ isExample: true,
5112
+ example: true,
5113
+ "@bit": { isExample: true },
5114
+ "@body-inline": { attrs: { isExample: true } }
5115
+ }
5116
+ },
5117
+ {
5118
+ isExample: true,
5119
+ example: "$",
5120
+ "@bit": { isExample: true },
5121
+ "@body-inline": { attrs: { isExample: true } }
5122
+ }
5123
+ ],
5124
+ description: "An example for the inline select option (first correct only)",
5125
+ format: TagFormat.boolean,
5126
+ nullable: true
5127
+ }
5128
+ ]
5129
+ },
5130
+ {
5131
+ exportJsonKey: {
5132
+ "@body-inline": {
5133
+ type: "select",
5134
+ attrs: { options: [{ text: "$", isCorrect: false }] }
5135
+ }
5136
+ },
5137
+ key: ConfigKey.tag_false,
5138
+ description: "False value for an inline select body bit",
5139
+ maxCount: Count.infinity,
5140
+ format: TagFormat.plainText,
5141
+ chain: [
5142
+ {
5143
+ key: ConfigKey.tag_true,
5144
+ exportJsonKey: {},
5145
+ description: "True values for an inline select body bit",
5146
+ maxCount: Count.infinity,
5147
+ format: TagFormat.plainText
5148
+ },
5149
+ {
5150
+ key: ConfigKey.tag_false,
5151
+ exportJsonKey: {},
5152
+ description: "False values for an inline select body bit",
5153
+ maxCount: Count.infinity,
5154
+ format: TagFormat.plainText
5155
+ },
5156
+ {
5157
+ key: ConfigKey.group_standardItemLeadInstructionHint,
5158
+ description: "Item, lead, page number, margin number, instruction and hint tags"
5159
+ },
5160
+ {
5161
+ // PLAN-083: identical to the `+` lead's chain @example —
5162
+ // cascade reaches the first correct option regardless of
5163
+ // which lead (+/-) opens the chain. Every firing rule bubbles
5164
+ // `@bit:{isExample:true}` and `@body-inline:{attrs:{isExample:true}}`.
5165
+ key: ConfigKey.property_example,
5166
+ jsonKey: "example",
5167
+ exportJsonKey: [
5168
+ {
5169
+ "@keyonly": {
5170
+ isExample: true,
5171
+ example: "$parent.isCorrect",
5172
+ "@bit": { isExample: true },
5173
+ "@body-inline": { attrs: { isExample: true } }
5174
+ }
5175
+ },
5176
+ {
5177
+ predicates: ["@absent", { "@parent.isCorrect": true }],
5178
+ maxEmits: 1,
5179
+ rule: {
5180
+ isExample: true,
5181
+ example: true,
5182
+ "@bit": { isExample: true },
5183
+ "@body-inline": { attrs: { isExample: true } }
5184
+ }
5185
+ },
5186
+ {
5187
+ isExample: true,
5188
+ example: "$",
5189
+ "@bit": { isExample: true },
5190
+ "@body-inline": { attrs: { isExample: true } }
5191
+ }
5192
+ ],
5193
+ description: "An example for the inline select option (first correct only)",
5194
+ format: TagFormat.boolean,
5195
+ nullable: true
5196
+ }
5197
+ ]
5198
+ }
5199
+ ]
5200
+ },
5201
+ [ConfigKey.group_trueFalseInlineHighlight]: {
5202
+ type: GroupConfigType.standard,
5203
+ description: 'True/False chain composed inline (highlight body-bit). +/- tags emit `{type:"highlight", attrs:{texts:[{text,isCorrect?}]}}` inline body nodes. Used by highlight-text bit.',
5204
+ tags: [
5205
+ {
5206
+ exportJsonKey: {
5207
+ "@body-inline": {
5208
+ type: "highlight",
5209
+ attrs: { texts: [{ text: "$", isCorrect: true }] }
5210
+ }
5211
+ },
5212
+ key: ConfigKey.tag_true,
5213
+ description: "Highlighted span in the body (correct)",
5214
+ maxCount: Count.infinity,
5215
+ format: TagFormat.plainText,
5216
+ chain: [
5217
+ {
5218
+ key: ConfigKey.tag_true,
5219
+ exportJsonKey: {},
5220
+ description: "Highlighted span values (correct)",
5221
+ maxCount: Count.infinity,
5222
+ format: TagFormat.plainText
5223
+ },
5224
+ {
5225
+ key: ConfigKey.tag_false,
5226
+ exportJsonKey: {},
5227
+ description: "Highlighted span values (incorrect)",
5228
+ maxCount: Count.infinity,
5229
+ format: TagFormat.plainText
5230
+ },
5231
+ {
5232
+ key: ConfigKey.group_standardItemLeadInstructionHint,
5233
+ description: "Item, lead, page number, margin number, instruction and hint tags"
5234
+ },
5235
+ {
5236
+ // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.13: cascade-fired
5237
+ // `@example` from the bit header fires `example: true` +
5238
+ // `isExample: true` on the FIRST correct highlight only
5239
+ // (BPG `fillBooleanExample(highlight.texts,
5240
+ // firstCorrectOnly=true)`). Incorrect highlights get
5241
+ // nothing (unlike choices, the highlight-text fixture
5242
+ // shows no `isExample` on incorrect entries).
5243
+ key: ConfigKey.property_example,
5244
+ jsonKey: "example",
5245
+ exportJsonKey: [
5246
+ {
5247
+ "@keyonly": {
5248
+ isExample: true,
5249
+ example: "$parent.isCorrect",
5250
+ "@bit": { isExample: true },
5251
+ "@body-inline": { attrs: { isExample: true } }
5252
+ }
5253
+ },
5254
+ {
5255
+ predicates: ["@absent", { "@parent.isCorrect": true }],
5256
+ maxEmits: 1,
5257
+ rule: {
5258
+ isExample: true,
5259
+ example: true,
5260
+ "@bit": { isExample: true },
5261
+ "@body-inline": { attrs: { isExample: true } }
5262
+ }
5263
+ },
5264
+ {
5265
+ isExample: true,
5266
+ example: "$",
5267
+ "@bit": { isExample: true },
5268
+ "@body-inline": { attrs: { isExample: true } }
5269
+ }
5270
+ ],
5271
+ description: "An example for the highlighted span (first correct only)",
5272
+ format: TagFormat.boolean,
5273
+ nullable: true
5274
+ }
5275
+ ]
5276
+ },
5277
+ {
5278
+ // PLAN-083 follow-up (2026-05-22): mirror the select `-` lead shape
5279
+ // by writing `isCorrect: false`. Without it, `$parent.isCorrect` in
5280
+ // chain-child @example rules resolves to ParentPathMissing for `-`
5281
+ // entries (e.g. `[-fish][@example]`), so the @keyonly rule falls
5282
+ // through and nothing fires. The natural-default strip removes
5283
+ // `isCorrect: false` from the final texts entries (matches fixture).
5284
+ exportJsonKey: {
5285
+ "@body-inline": {
5286
+ type: "highlight",
5287
+ attrs: { texts: [{ text: "$", isCorrect: false }] }
5288
+ }
5289
+ },
5290
+ key: ConfigKey.tag_false,
5291
+ description: "Highlighted span in the body (incorrect)",
5292
+ maxCount: Count.infinity,
5293
+ format: TagFormat.plainText,
5294
+ chain: [
5295
+ {
5296
+ key: ConfigKey.tag_true,
5297
+ exportJsonKey: {},
5298
+ description: "Highlighted span values (correct)",
5299
+ maxCount: Count.infinity,
3757
5300
  format: TagFormat.plainText
5301
+ },
5302
+ {
5303
+ key: ConfigKey.tag_false,
5304
+ exportJsonKey: {},
5305
+ description: "Highlighted span values (incorrect)",
5306
+ maxCount: Count.infinity,
5307
+ format: TagFormat.plainText
5308
+ },
5309
+ {
5310
+ key: ConfigKey.group_standardItemLeadInstructionHint,
5311
+ description: "Item, lead, page number, margin number, instruction and hint tags"
5312
+ },
5313
+ {
5314
+ // PLAN-072 + ALIGN-EXAMPLE-CASCADE §3.13: cascade-fired
5315
+ // `@example` on a `-` (incorrect highlight) emits nothing
5316
+ // (highlight-text fixture has no `isExample` on incorrect
5317
+ // entries; firstCorrectOnly cascade skips them entirely).
5318
+ // No `@absent` rule.
5319
+ // PLAN-083: identical to the `+` lead's chain @example —
5320
+ // cascade reaches the first correct option regardless of
5321
+ // which lead (+/-) opens the chain.
5322
+ key: ConfigKey.property_example,
5323
+ jsonKey: "example",
5324
+ exportJsonKey: [
5325
+ {
5326
+ "@keyonly": {
5327
+ isExample: true,
5328
+ example: "$parent.isCorrect",
5329
+ "@bit": { isExample: true },
5330
+ "@body-inline": { attrs: { isExample: true } }
5331
+ }
5332
+ },
5333
+ {
5334
+ predicates: ["@absent", { "@parent.isCorrect": true }],
5335
+ maxEmits: 1,
5336
+ rule: {
5337
+ isExample: true,
5338
+ example: true,
5339
+ "@bit": { isExample: true },
5340
+ "@body-inline": { attrs: { isExample: true } }
5341
+ }
5342
+ },
5343
+ {
5344
+ isExample: true,
5345
+ example: "$",
5346
+ "@bit": { isExample: true },
5347
+ "@body-inline": { attrs: { isExample: true } }
5348
+ }
5349
+ ],
5350
+ description: "An example for the highlighted span (first correct only)",
5351
+ format: TagFormat.boolean,
5352
+ nullable: true
3758
5353
  }
3759
5354
  ]
3760
5355
  }
@@ -3767,6 +5362,7 @@ var GROUPS = {
3767
5362
  {
3768
5363
  key: ConfigKey.property_mark,
3769
5364
  jsonKey: "marks.mark",
5365
+ exportJsonKey: { marks: [{ mark: "$" }] },
3770
5366
  description: "The mark configuration",
3771
5367
  format: TagFormat.plainText,
3772
5368
  maxCount: Count.infinity,
@@ -3790,21 +5386,32 @@ var GROUPS = {
3790
5386
  description: "Mark chain",
3791
5387
  tags: [
3792
5388
  {
5389
+ exportJsonKey: {
5390
+ "@body-inline": { type: "mark", attrs: { solution: "$" } }
5391
+ },
3793
5392
  key: ConfigKey.tag_mark,
3794
5393
  description: "The marked content",
3795
5394
  maxCount: Count.infinity,
5395
+ format: TagFormat.plainText,
3796
5396
  chain: [
3797
5397
  {
3798
5398
  key: ConfigKey.property_mark,
3799
5399
  jsonKey: "mark",
5400
+ exportJsonKey: { mark: "$" },
3800
5401
  description: "The mark configuration",
3801
5402
  format: TagFormat.plainText
3802
5403
  },
3803
5404
  {
3804
5405
  key: ConfigKey.property_example,
3805
5406
  jsonKey: "example",
5407
+ exportJsonKey: [
5408
+ { "@keyonly": { isExample: true, example: true, "@bit": { isExample: true } } },
5409
+ { "@absent": { isExample: true, example: true, "@bit": { isExample: true } } },
5410
+ { isExample: true, example: "$", "@bit": { isExample: true } }
5411
+ ],
3806
5412
  description: "An example for the marked content",
3807
- format: TagFormat.plainText
5413
+ format: TagFormat.bitmarkText,
5414
+ nullable: true
3808
5415
  }
3809
5416
  ]
3810
5417
  }
@@ -3893,7 +5500,8 @@ var GROUPS = {
3893
5500
  name: "Title",
3894
5501
  description: "The title of the book",
3895
5502
  maxCount: 2,
3896
- jsonKey: "title|multi(count=2, key=subtitle)"
5503
+ jsonKey: "title|multi(count=2, key=subtitle)",
5504
+ exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
3897
5505
  },
3898
5506
  {
3899
5507
  key: ConfigKey.property_subtype,
@@ -3909,6 +5517,7 @@ var GROUPS = {
3909
5517
  {
3910
5518
  key: ConfigKey.resource_coverImage,
3911
5519
  jsonKey: "coverImage|resource(type=image, key=image)",
5520
+ exportJsonKey: { coverImage: { type: "image", image: { src: "$" } } },
3912
5521
  description: "The cover image of the book",
3913
5522
  chain: [
3914
5523
  {
@@ -3926,6 +5535,7 @@ var GROUPS = {
3926
5535
  {
3927
5536
  key: ConfigKey.resource_backgroundImage,
3928
5537
  jsonKey: "backgroundImage|resource(type=image, key=image)",
5538
+ exportJsonKey: { backgroundImage: { type: "image", image: { src: "$" } } },
3929
5539
  description: "The background image of the book",
3930
5540
  chain: [
3931
5541
  {
@@ -4085,10 +5695,21 @@ var GROUPS = {
4085
5695
  format: TagFormat.plainText,
4086
5696
  chain: [
4087
5697
  {
5698
+ exportJsonKey: { reference: "$" },
4088
5699
  key: ConfigKey.tag_reference,
4089
5700
  format: TagFormat.plainText,
4090
- description: "The reference(s) for the book(s) in the learning path",
4091
- maxCount: 2
5701
+ description: "The reference for the book(s) in the learning path",
5702
+ maxCount: 1,
5703
+ chain: [
5704
+ {
5705
+ key: ConfigKey.tag_reference,
5706
+ jsonKey: "referenceEnd",
5707
+ exportJsonKey: { referenceEnd: "$" },
5708
+ format: TagFormat.plainText,
5709
+ description: "The referenceEnd for the book(s) in the learning path",
5710
+ maxCount: 1
5711
+ }
5712
+ ]
4092
5713
  }
4093
5714
  ]
4094
5715
  }
@@ -4219,6 +5840,42 @@ var GROUPS = {
4219
5840
  }
4220
5841
  ]
4221
5842
  },
5843
+ [ConfigKey.group_backgroundWallpaper]: {
5844
+ type: GroupConfigType.standard,
5845
+ description: "Background wallpaper tags",
5846
+ tags: [
5847
+ {
5848
+ key: ConfigKey.resource_backgroundWallpaper,
5849
+ exportJsonKey: { backgroundWallpaper: { type: "image", image: { src: "$" } } },
5850
+ description: "Background wallpaper for the image, used to set a background for the image",
5851
+ chain: [
5852
+ {
5853
+ key: ConfigKey.group_resourceImageCommon,
5854
+ description: "Common resource image tags for images"
5855
+ }
5856
+ ]
5857
+ }
5858
+ ]
5859
+ },
5860
+ [ConfigKey.group_imageNoZoom]: {
5861
+ type: GroupConfigType.standard,
5862
+ description: "Image bit tags",
5863
+ tags: [
5864
+ {
5865
+ key: ConfigKey.group_backgroundWallpaper,
5866
+ description: "Background wallpaper tags for images, used to define background properties for images"
5867
+ },
5868
+ {
5869
+ key: ConfigKey.group_resourceBitTags,
5870
+ description: "Resource bit tags for images, used to define additional properties for images"
5871
+ },
5872
+ {
5873
+ key: ConfigKey.group_resourceImageNoZoom,
5874
+ description: "Resource image tags for images, used to attach images to the bit",
5875
+ minCount: 1
5876
+ }
5877
+ ]
5878
+ },
4222
5879
  //
4223
5880
  // Resource groups
4224
5881
  //
@@ -4228,6 +5885,8 @@ var GROUPS = {
4228
5885
  tags: [
4229
5886
  {
4230
5887
  key: ConfigKey.resource_imagePlaceholder,
5888
+ jsonKey: "imagePlaceholder|resource(type=image, key=image)",
5889
+ exportJsonKey: { imagePlaceholder: { type: "image", image: { src: "$" } } },
4231
5890
  description: "Placeholder image for the resource",
4232
5891
  chain: [
4233
5892
  {
@@ -4336,6 +5995,58 @@ var GROUPS = {
4336
5995
  }
4337
5996
  ]
4338
5997
  },
5998
+ [ConfigKey.group_resourceImageCommonNoZoom]: {
5999
+ type: GroupConfigType.standard,
6000
+ description: "Common properties for image resources where @zoomDisabled defaults to true",
6001
+ tags: [
6002
+ {
6003
+ key: ConfigKey.group_resourceCommon,
6004
+ description: "Common resource properties"
6005
+ },
6006
+ {
6007
+ key: ConfigKey.property_src1x,
6008
+ description: "The source URL for the image at 1x resolution",
6009
+ format: TagFormat.plainText
6010
+ },
6011
+ {
6012
+ key: ConfigKey.property_src2x,
6013
+ description: "The source URL for the image at 2x resolution",
6014
+ format: TagFormat.plainText
6015
+ },
6016
+ {
6017
+ key: ConfigKey.property_src3x,
6018
+ description: "The source URL for the image at 3x resolution",
6019
+ format: TagFormat.plainText
6020
+ },
6021
+ {
6022
+ key: ConfigKey.property_src4x,
6023
+ description: "The source URL for the image at 4x resolution",
6024
+ format: TagFormat.plainText
6025
+ },
6026
+ {
6027
+ key: ConfigKey.property_width,
6028
+ description: "The width of the image",
6029
+ format: TagFormat.number
6030
+ },
6031
+ {
6032
+ key: ConfigKey.property_height,
6033
+ description: "The height of the image",
6034
+ format: TagFormat.number
6035
+ },
6036
+ {
6037
+ key: ConfigKey.property_alt,
6038
+ description: "The alternative text for the image",
6039
+ format: TagFormat.plainText
6040
+ },
6041
+ // @zoomDisabled with a true default
6042
+ {
6043
+ key: ConfigKey.property_zoomDisabled,
6044
+ description: "If true, zooming is disabled for the image (defaults to true)",
6045
+ format: TagFormat.boolean,
6046
+ defaultValue: "true"
6047
+ }
6048
+ ]
6049
+ },
4339
6050
  [ConfigKey.group_resourceAudioCommon]: {
4340
6051
  type: GroupConfigType.standard,
4341
6052
  description: "Common properties for audio resources",
@@ -4412,6 +6123,7 @@ var GROUPS = {
4412
6123
  {
4413
6124
  key: ConfigKey.property_posterImage,
4414
6125
  jsonKey: "posterImage.src",
6126
+ exportJsonKey: { posterImage: { src: "$" } },
4415
6127
  description: "The poster image for the video",
4416
6128
  format: TagFormat.plainText,
4417
6129
  chain: [
@@ -4424,22 +6136,26 @@ var GROUPS = {
4424
6136
  {
4425
6137
  key: ConfigKey.property_src1x,
4426
6138
  description: "The source URL for the video at 1x resolution",
4427
- format: TagFormat.plainText
6139
+ format: TagFormat.plainText,
6140
+ exportJsonKey: { thumbnails: [{ src: "$" }] }
4428
6141
  },
4429
6142
  {
4430
6143
  key: ConfigKey.property_src2x,
4431
6144
  description: "The source URL for the video at 2x resolution",
4432
- format: TagFormat.plainText
6145
+ format: TagFormat.plainText,
6146
+ exportJsonKey: { thumbnails: [{ src: "$" }] }
4433
6147
  },
4434
6148
  {
4435
6149
  key: ConfigKey.property_src3x,
4436
6150
  description: "The source URL for the video at 3x resolution",
4437
- format: TagFormat.plainText
6151
+ format: TagFormat.plainText,
6152
+ exportJsonKey: { thumbnails: [{ src: "$" }] }
4438
6153
  },
4439
6154
  {
4440
6155
  key: ConfigKey.property_src4x,
4441
6156
  description: "The source URL for the video at 4x resolution",
4442
- format: TagFormat.plainText
6157
+ format: TagFormat.plainText,
6158
+ exportJsonKey: { thumbnails: [{ src: "$" }] }
4443
6159
  }
4444
6160
  ]
4445
6161
  },
@@ -4452,6 +6168,7 @@ var GROUPS = {
4452
6168
  tags: [
4453
6169
  {
4454
6170
  key: ConfigKey.resource_icon,
6171
+ exportJsonKey: { resource: { type: "icon", icon: { src: "$" } } },
4455
6172
  description: "The icon resource",
4456
6173
  chain: [
4457
6174
  {
@@ -4468,6 +6185,7 @@ var GROUPS = {
4468
6185
  tags: [
4469
6186
  {
4470
6187
  key: ConfigKey.resource_image,
6188
+ exportJsonKey: { resource: { type: "image", image: { src: "$" } } },
4471
6189
  description: "The image resource",
4472
6190
  chain: [
4473
6191
  {
@@ -4478,12 +6196,30 @@ var GROUPS = {
4478
6196
  }
4479
6197
  ]
4480
6198
  },
6199
+ [ConfigKey.group_resourceImageNoZoom]: {
6200
+ type: GroupConfigType.resource,
6201
+ description: "Image resource where @zoomDisabled defaults to true",
6202
+ tags: [
6203
+ {
6204
+ key: ConfigKey.resource_image,
6205
+ exportJsonKey: { resource: { type: "image", image: { src: "$" } } },
6206
+ description: "The image resource (no-zoom default)",
6207
+ chain: [
6208
+ {
6209
+ key: ConfigKey.group_resourceImageCommonNoZoom,
6210
+ description: "Common image properties with @zoomDisabled defaulting to true"
6211
+ }
6212
+ ]
6213
+ }
6214
+ ]
6215
+ },
4481
6216
  [ConfigKey.group_resourceImagePortrait]: {
4482
6217
  type: GroupConfigType.resource,
4483
6218
  description: "Portrait image resource",
4484
6219
  tags: [
4485
6220
  {
4486
6221
  key: ConfigKey.resource_imagePortrait,
6222
+ exportJsonKey: { resource: { type: "image-portrait", imagePortrait: { src: "$" } } },
4487
6223
  description: "The portrait image resource",
4488
6224
  chain: [
4489
6225
  {
@@ -4500,6 +6236,7 @@ var GROUPS = {
4500
6236
  tags: [
4501
6237
  {
4502
6238
  key: ConfigKey.resource_imageLandscape,
6239
+ exportJsonKey: { resource: { type: "image-landscape", imageLandscape: { src: "$" } } },
4503
6240
  description: "The landscape image resource",
4504
6241
  chain: [
4505
6242
  {
@@ -4516,6 +6253,7 @@ var GROUPS = {
4516
6253
  tags: [
4517
6254
  {
4518
6255
  key: ConfigKey.resource_imageEmbed,
6256
+ exportJsonKey: { resource: { type: "image-embed", imageEmbed: { src: "$" } } },
4519
6257
  description: "The embedded image resource",
4520
6258
  chain: [
4521
6259
  {
@@ -4532,6 +6270,7 @@ var GROUPS = {
4532
6270
  tags: [
4533
6271
  {
4534
6272
  key: ConfigKey.resource_imageLink,
6273
+ exportJsonKey: { resource: { type: "image-link", imageLink: { url: "$" } } },
4535
6274
  description: "The link to the image resource",
4536
6275
  chain: [
4537
6276
  {
@@ -4548,6 +6287,7 @@ var GROUPS = {
4548
6287
  tags: [
4549
6288
  {
4550
6289
  key: ConfigKey.resource_audio,
6290
+ exportJsonKey: { resource: { type: "audio", audio: { src: "$" } } },
4551
6291
  description: "The audio resource",
4552
6292
  chain: [
4553
6293
  {
@@ -4564,6 +6304,7 @@ var GROUPS = {
4564
6304
  tags: [
4565
6305
  {
4566
6306
  key: ConfigKey.resource_audioEmbed,
6307
+ exportJsonKey: { resource: { type: "audio-embed", audioEmbed: { src: "$" } } },
4567
6308
  description: "The embedded audio resource",
4568
6309
  chain: [
4569
6310
  {
@@ -4580,6 +6321,7 @@ var GROUPS = {
4580
6321
  tags: [
4581
6322
  {
4582
6323
  key: ConfigKey.resource_audioLink,
6324
+ exportJsonKey: { resource: { type: "audio-link", audioLink: { url: "$" } } },
4583
6325
  description: "The link to the audio resource",
4584
6326
  chain: [
4585
6327
  {
@@ -4596,6 +6338,7 @@ var GROUPS = {
4596
6338
  tags: [
4597
6339
  {
4598
6340
  key: ConfigKey.resource_video,
6341
+ exportJsonKey: { resource: { type: "video", video: { src: "$" } } },
4599
6342
  description: "The video resource",
4600
6343
  chain: [
4601
6344
  {
@@ -4612,6 +6355,7 @@ var GROUPS = {
4612
6355
  tags: [
4613
6356
  {
4614
6357
  key: ConfigKey.resource_videoEmbed,
6358
+ exportJsonKey: { resource: { type: "video-embed", videoEmbed: { url: "$" } } },
4615
6359
  description: "The embedded video resource",
4616
6360
  chain: [
4617
6361
  {
@@ -4628,6 +6372,7 @@ var GROUPS = {
4628
6372
  tags: [
4629
6373
  {
4630
6374
  key: ConfigKey.resource_videoLink,
6375
+ exportJsonKey: { resource: { type: "video-link", videoLink: { url: "$" } } },
4631
6376
  description: "The link to the video resource",
4632
6377
  chain: [
4633
6378
  {
@@ -4644,6 +6389,9 @@ var GROUPS = {
4644
6389
  tags: [
4645
6390
  {
4646
6391
  key: ConfigKey.resource_stillImageFilmEmbed,
6392
+ exportJsonKey: {
6393
+ resource: { type: "still-image-film-embed", stillImageFilmEmbed: { url: "$" } }
6394
+ },
4647
6395
  description: "The embedded still image film resource",
4648
6396
  chain: [
4649
6397
  {
@@ -4660,6 +6408,9 @@ var GROUPS = {
4660
6408
  tags: [
4661
6409
  {
4662
6410
  key: ConfigKey.resource_stillImageFilmLink,
6411
+ exportJsonKey: {
6412
+ resource: { type: "still-image-film-link", stillImageFilmLink: { url: "$" } }
6413
+ },
4663
6414
  description: "The link to the still image film resource",
4664
6415
  chain: [
4665
6416
  {
@@ -4676,6 +6427,7 @@ var GROUPS = {
4676
6427
  tags: [
4677
6428
  {
4678
6429
  key: ConfigKey.resource_article,
6430
+ exportJsonKey: { resource: { type: "article", article: { body: "$" } } },
4679
6431
  description: "The article resource",
4680
6432
  chain: [
4681
6433
  {
@@ -4692,6 +6444,7 @@ var GROUPS = {
4692
6444
  tags: [
4693
6445
  {
4694
6446
  key: ConfigKey.resource_articleEmbed,
6447
+ exportJsonKey: { resource: { type: "article-embed", articleEmbed: { url: "$" } } },
4695
6448
  description: "The embedded article resource",
4696
6449
  chain: [
4697
6450
  {
@@ -4708,6 +6461,7 @@ var GROUPS = {
4708
6461
  tags: [
4709
6462
  {
4710
6463
  key: ConfigKey.resource_articleLink,
6464
+ exportJsonKey: { resource: { type: "article-link", articleLink: { url: "$" } } },
4711
6465
  description: "The link to the article resource",
4712
6466
  chain: [
4713
6467
  {
@@ -4724,6 +6478,7 @@ var GROUPS = {
4724
6478
  tags: [
4725
6479
  {
4726
6480
  key: ConfigKey.resource_document,
6481
+ exportJsonKey: { resource: { type: "document", document: { url: "$" } } },
4727
6482
  description: "The document resource",
4728
6483
  chain: [
4729
6484
  {
@@ -4740,6 +6495,7 @@ var GROUPS = {
4740
6495
  tags: [
4741
6496
  {
4742
6497
  key: ConfigKey.resource_documentEmbed,
6498
+ exportJsonKey: { resource: { type: "document-embed", documentEmbed: { url: "$" } } },
4743
6499
  description: "The embedded document resource",
4744
6500
  chain: [
4745
6501
  {
@@ -4756,6 +6512,7 @@ var GROUPS = {
4756
6512
  tags: [
4757
6513
  {
4758
6514
  key: ConfigKey.resource_documentLink,
6515
+ exportJsonKey: { resource: { type: "document-link", documentLink: { url: "$" } } },
4759
6516
  description: "The link to the document resource",
4760
6517
  chain: [
4761
6518
  {
@@ -4772,6 +6529,7 @@ var GROUPS = {
4772
6529
  tags: [
4773
6530
  {
4774
6531
  key: ConfigKey.resource_documentDownload,
6532
+ exportJsonKey: { resource: { type: "document-download", documentDownload: { url: "$" } } },
4775
6533
  description: "The downloadable document resource",
4776
6534
  chain: [
4777
6535
  {
@@ -4788,6 +6546,7 @@ var GROUPS = {
4788
6546
  tags: [
4789
6547
  {
4790
6548
  key: ConfigKey.resource_appLink,
6549
+ exportJsonKey: { resource: { type: "app-link", appLink: { url: "$" } } },
4791
6550
  description: "The link to the app resource",
4792
6551
  chain: [
4793
6552
  {
@@ -4804,6 +6563,7 @@ var GROUPS = {
4804
6563
  tags: [
4805
6564
  {
4806
6565
  key: ConfigKey.resource_websiteLink,
6566
+ exportJsonKey: { resource: { type: "website-link", websiteLink: { url: "$" } } },
4807
6567
  description: "The link to the website resource",
4808
6568
  chain: [
4809
6569
  {
@@ -4862,6 +6622,7 @@ var GROUPS = {
4862
6622
  {
4863
6623
  key: ConfigKey.resource_previewImage,
4864
6624
  jsonKey: "previewImage|resource(type=image, key=image)",
6625
+ exportJsonKey: { previewImage: { type: "image", image: { src: "$" } } },
4865
6626
  description: "The preview image resource",
4866
6627
  chain: [
4867
6628
  {
@@ -4879,6 +6640,7 @@ var GROUPS = {
4879
6640
  {
4880
6641
  key: ConfigKey.resource_previewVideo,
4881
6642
  jsonKey: "previewVideo|resource(type=video, key=video)",
6643
+ exportJsonKey: { previewVideo: { type: "video", video: { src: "$" } } },
4882
6644
  description: "The preview video resource",
4883
6645
  chain: [
4884
6646
  {
@@ -4943,20 +6705,53 @@ var ConfigHydrator = class {
4943
6705
  _side.name,
4944
6706
  _side.repeat ?? false,
4945
6707
  variantsOfSide,
4946
- _side.jsonKey
6708
+ _side.jsonKey,
6709
+ _side.exportJsonKey,
6710
+ Object.prototype.hasOwnProperty.call(_side, "exportJsonKey")
4947
6711
  );
4948
6712
  sides.push(sideConfig);
4949
6713
  }
4950
6714
  const cardSetConfig = new CardSetConfig(
4951
6715
  _cardSet,
4952
6716
  _cardSetConfig.jsonKey,
4953
- _cardSetConfig.sections,
6717
+ _cardSetConfig.exportJsonKey,
6718
+ Object.prototype.hasOwnProperty.call(_cardSetConfig, "exportJsonKey"),
6719
+ this.hydrateCardSections(_cardSetConfig.sections),
4954
6720
  sides
4955
6721
  );
4956
6722
  return cardSetConfig;
4957
6723
  }
6724
+ hydrateCardSections(sections) {
6725
+ if (!sections) return void 0;
6726
+ const out = {};
6727
+ for (const [k, v] of Object.entries(sections)) {
6728
+ out[k] = {
6729
+ jsonKey: v.jsonKey,
6730
+ exportJsonKey: v.exportJsonKey,
6731
+ hasExportJsonKey: Object.prototype.hasOwnProperty.call(v, "exportJsonKey"),
6732
+ isDefault: v.isDefault,
6733
+ sideJsonKey: v.sideJsonKey,
6734
+ sideExportJsonKey: v.sideExportJsonKey,
6735
+ hasSideExportJsonKey: Object.prototype.hasOwnProperty.call(v, "sideExportJsonKey"),
6736
+ // PLAN-085: cardinality fields pass through; defaults applied
6737
+ // downstream by the config consumer (Rust treats `0` / undefined
6738
+ // as unbounded).
6739
+ minCount: v.minCount,
6740
+ maxCount: v.maxCount
6741
+ };
6742
+ }
6743
+ return out;
6744
+ }
4958
6745
  hydrateTagConfig(_tag) {
4959
- const { key: _configKey, maxCount, minCount, chain: _chain, deprecated } = _tag;
6746
+ const {
6747
+ key: _configKey,
6748
+ maxCount,
6749
+ minCount,
6750
+ chain: _chain,
6751
+ deprecated,
6752
+ jsonKey,
6753
+ exportJsonKey
6754
+ } = _tag;
4960
6755
  const configKey = (0, import_superenum15.Enum)(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
4961
6756
  if (!configKey) throw new Error(`No tag key found for config key '${configKey}'`);
4962
6757
  const tag = (0, import_superenum15.Enum)(Tag).fromValue(configKey);
@@ -4971,6 +6766,9 @@ var ConfigHydrator = class {
4971
6766
  maxCount: maxCount ?? MAX_COUNT_DEFAULT,
4972
6767
  minCount: minCount ?? MIN_COUNT_DEFAULT,
4973
6768
  chain,
6769
+ jsonKey,
6770
+ exportJsonKey,
6771
+ hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
4974
6772
  deprecated
4975
6773
  });
4976
6774
  return {
@@ -4989,7 +6787,8 @@ var ConfigHydrator = class {
4989
6787
  format,
4990
6788
  values,
4991
6789
  defaultValue,
4992
- jsonKey
6790
+ jsonKey,
6791
+ exportJsonKey
4993
6792
  } = _tag;
4994
6793
  const configKey = (0, import_superenum15.Enum)(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
4995
6794
  if (!configKey) throw new Error(`No property key found for config key '${configKey}'`);
@@ -5005,6 +6804,8 @@ var ConfigHydrator = class {
5005
6804
  minCount: minCount ?? MIN_COUNT_DEFAULT,
5006
6805
  chain,
5007
6806
  jsonKey,
6807
+ exportJsonKey,
6808
+ hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
5008
6809
  format,
5009
6810
  values,
5010
6811
  defaultValue,
@@ -5017,7 +6818,15 @@ var ConfigHydrator = class {
5017
6818
  };
5018
6819
  }
5019
6820
  hydrateResourceTagConfig(_tag) {
5020
- const { key: _configKey, maxCount, minCount, chain: _chain, deprecated, jsonKey } = _tag;
6821
+ const {
6822
+ key: _configKey,
6823
+ maxCount,
6824
+ minCount,
6825
+ chain: _chain,
6826
+ deprecated,
6827
+ jsonKey,
6828
+ exportJsonKey
6829
+ } = _tag;
5021
6830
  const configKey = (0, import_superenum15.Enum)(ConfigKey).fromValue(_configKey) || ConfigKey._unknown;
5022
6831
  if (!configKey) throw new Error(`No resource key found for config key '${configKey}'`);
5023
6832
  const tag = _configKey.substring(1);
@@ -5032,6 +6841,8 @@ var ConfigHydrator = class {
5032
6841
  minCount: minCount ?? MIN_COUNT_DEFAULT,
5033
6842
  chain,
5034
6843
  jsonKey,
6844
+ exportJsonKey,
6845
+ hasExportJsonKey: Object.prototype.hasOwnProperty.call(_tag, "exportJsonKey"),
5035
6846
  deprecated
5036
6847
  });
5037
6848
  return {
@@ -5063,14 +6874,25 @@ var ConfigHydrator = class {
5063
6874
  };
5064
6875
  }
5065
6876
  hydrateCardVariantConfig(_variant) {
5066
- const { tags: _tags, bodyAllowed, bodyRequired, repeatCount, jsonKey } = _variant;
6877
+ const {
6878
+ tags: _tags,
6879
+ bodyAllowed,
6880
+ bodyRequired,
6881
+ repeatCount,
6882
+ jsonKey,
6883
+ exportJsonKey,
6884
+ format
6885
+ } = _variant;
5067
6886
  const tags2 = this.hydrateTagsConfig(_tags);
5068
6887
  const cardSetConfig = new CardVariantConfig(
5069
6888
  tags2.tags,
5070
6889
  bodyAllowed,
5071
6890
  bodyRequired,
5072
6891
  repeatCount,
5073
- jsonKey
6892
+ jsonKey,
6893
+ exportJsonKey,
6894
+ Object.prototype.hasOwnProperty.call(_variant, "exportJsonKey"),
6895
+ format
5074
6896
  );
5075
6897
  return cardSetConfig;
5076
6898
  }
@@ -5108,6 +6930,7 @@ var BITS = {
5108
6930
  description: "Common tags for quiz bits"
5109
6931
  },
5110
6932
  {
6933
+ exportJsonKey: { title: "$" },
5111
6934
  key: ConfigKey.tag_title,
5112
6935
  description: "The title of the flashcard quiz"
5113
6936
  },
@@ -5116,6 +6939,21 @@ var BITS = {
5116
6939
  description: "The flashcard set to use for the app flashcards",
5117
6940
  format: TagFormat.plainText,
5118
6941
  maxCount: Count.infinity
6942
+ },
6943
+ {
6944
+ // Override inherited @example from group_standardTags: string root
6945
+ // example. Bare/synthesized emits `example: "true"` (a text literal
6946
+ // that field-coerces to a paragraph "true" in bitmark+ contexts);
6947
+ // valued [@example:text] emits the user's text via `$`.
6948
+ key: ConfigKey.property_example,
6949
+ exportJsonKey: [
6950
+ { "@keyonly": { isExample: true, example: "true" } },
6951
+ { "@absent": { isExample: true, example: "true" } },
6952
+ { isExample: true, example: "$" }
6953
+ ],
6954
+ description: "The example text for the bit",
6955
+ format: TagFormat.plainText,
6956
+ nullable: true
5119
6957
  }
5120
6958
  ],
5121
6959
  rootExampleType: ExampleType.string
@@ -5197,6 +7035,7 @@ var BITS = {
5197
7035
  description: "Article bit, used for articles / paragraphs",
5198
7036
  tags: [
5199
7037
  {
7038
+ exportJsonKey: { title: "$" },
5200
7039
  key: ConfigKey.tag_title,
5201
7040
  description: "The title of the article"
5202
7041
  }
@@ -5304,7 +7143,8 @@ var BITS = {
5304
7143
  key: ConfigKey.tag_title,
5305
7144
  description: "Title and subtitle of the catalog item",
5306
7145
  maxCount: 2,
5307
- jsonKey: "title|multi(count=2, key=subtitle)"
7146
+ jsonKey: "title|multi(count=2, key=subtitle)",
7147
+ exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
5308
7148
  },
5309
7149
  {
5310
7150
  key: ConfigKey.property_coverImage,
@@ -5314,6 +7154,7 @@ var BITS = {
5314
7154
  {
5315
7155
  key: ConfigKey.resource_coverImage,
5316
7156
  jsonKey: "coverImage|resource(type=image, key=image)",
7157
+ exportJsonKey: { coverImage: { type: "image", image: { src: "$" } } },
5317
7158
  description: "Cover image of the catalog item",
5318
7159
  chain: [
5319
7160
  {
@@ -5441,6 +7282,18 @@ var BITS = {
5441
7282
  key: ConfigKey.group_previewVideos,
5442
7283
  description: "Array of preview videos for the catalog item",
5443
7284
  maxCount: Count.infinity
7285
+ },
7286
+ {
7287
+ // Override inherited @example from group_standardTags: string root example.
7288
+ key: ConfigKey.property_example,
7289
+ exportJsonKey: [
7290
+ { "@keyonly": { isExample: true, example: "true" } },
7291
+ { "@absent": { isExample: true, example: "true" } },
7292
+ { isExample: true, example: "$" }
7293
+ ],
7294
+ description: "The example text for the bit",
7295
+ format: TagFormat.plainText,
7296
+ nullable: true
5444
7297
  }
5445
7298
  ],
5446
7299
  rootExampleType: ExampleType.string
@@ -5454,7 +7307,8 @@ var BITS = {
5454
7307
  key: ConfigKey.tag_title,
5455
7308
  description: "Title and subtitle of the catalog item",
5456
7309
  maxCount: 2,
5457
- jsonKey: "title|multi(count=2, key=subtitle)"
7310
+ jsonKey: "title|multi(count=2, key=subtitle)",
7311
+ exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
5458
7312
  },
5459
7313
  {
5460
7314
  key: ConfigKey.property_coverImage,
@@ -5464,6 +7318,7 @@ var BITS = {
5464
7318
  {
5465
7319
  key: ConfigKey.resource_coverImage,
5466
7320
  jsonKey: "coverImage|resource(type=image, key=image)",
7321
+ exportJsonKey: { coverImage: { type: "image", image: { src: "$" } } },
5467
7322
  description: "Cover image of the catalog item",
5468
7323
  chain: [
5469
7324
  {
@@ -5591,6 +7446,18 @@ var BITS = {
5591
7446
  key: ConfigKey.group_previewVideos,
5592
7447
  description: "Array of preview videos for the catalog item",
5593
7448
  maxCount: Count.infinity
7449
+ },
7450
+ {
7451
+ // Override inherited @example from group_standardTags: string root example.
7452
+ key: ConfigKey.property_example,
7453
+ exportJsonKey: [
7454
+ { "@keyonly": { isExample: true, example: "true" } },
7455
+ { "@absent": { isExample: true, example: "true" } },
7456
+ { isExample: true, example: "$" }
7457
+ ],
7458
+ description: "The example text for the bit",
7459
+ format: TagFormat.plainText,
7460
+ nullable: true
5594
7461
  }
5595
7462
  ],
5596
7463
  rootExampleType: ExampleType.string
@@ -5604,7 +7471,8 @@ var BITS = {
5604
7471
  key: ConfigKey.tag_title,
5605
7472
  description: "Title and subtitle of the catalog item",
5606
7473
  maxCount: 2,
5607
- jsonKey: "title|multi(count=2, key=subtitle)"
7474
+ jsonKey: "title|multi(count=2, key=subtitle)",
7475
+ exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
5608
7476
  },
5609
7477
  {
5610
7478
  key: ConfigKey.property_coverImage,
@@ -5730,6 +7598,18 @@ var BITS = {
5730
7598
  key: ConfigKey.group_previewVideos,
5731
7599
  description: "Array of preview videos for the catalog item",
5732
7600
  maxCount: Count.infinity
7601
+ },
7602
+ {
7603
+ // Override inherited @example from group_standardTags: string root example.
7604
+ key: ConfigKey.property_example,
7605
+ exportJsonKey: [
7606
+ { "@keyonly": { isExample: true, example: "true" } },
7607
+ { "@absent": { isExample: true, example: "true" } },
7608
+ { isExample: true, example: "$" }
7609
+ ],
7610
+ description: "The example text for the bit",
7611
+ format: TagFormat.plainText,
7612
+ nullable: true
5733
7613
  }
5734
7614
  ],
5735
7615
  rootExampleType: ExampleType.string
@@ -5931,11 +7811,13 @@ var BITS = {
5931
7811
  description: "Bit alias, used to create an alias for a bit",
5932
7812
  tags: [
5933
7813
  {
7814
+ exportJsonKey: { reference: "$" },
5934
7815
  key: ConfigKey.tag_reference,
5935
7816
  format: TagFormat.plainText,
5936
7817
  description: "The reference to the bit that this alias points to"
5937
7818
  },
5938
7819
  {
7820
+ exportJsonKey: { anchor: "$" },
5939
7821
  key: ConfigKey.tag_anchor,
5940
7822
  format: TagFormat.plainText,
5941
7823
  description: "The anchor for the bit alias, used for linking"
@@ -6137,7 +8019,8 @@ var BITS = {
6137
8019
  description: "The title of the book cover",
6138
8020
  maxCount: 2,
6139
8021
  // title & subtitle
6140
- jsonKey: "title|multi(count=2, key=subtitle)"
8022
+ jsonKey: "title|multi(count=2, key=subtitle)",
8023
+ exportJsonKey: [{ "@level=1": { title: "$" } }, { "@level=2": { subtitle: "$" } }]
6141
8024
  },
6142
8025
  {
6143
8026
  key: ConfigKey.property_coverColor,
@@ -6281,6 +8164,7 @@ var BITS = {
6281
8164
  description: "Chapter bit, used to define chapters in books or articles",
6282
8165
  tags: [
6283
8166
  {
8167
+ exportJsonKey: { anchor: "$" },
6284
8168
  key: ConfigKey.tag_anchor,
6285
8169
  format: TagFormat.plainText,
6286
8170
  description: "The anchor for the chapter, used for linking"
@@ -6288,7 +8172,8 @@ var BITS = {
6288
8172
  {
6289
8173
  key: ConfigKey.tag_title,
6290
8174
  description: "The title of the chapter",
6291
- jsonKey: "title|setMulti(level)"
8175
+ jsonKey: "title|setMulti(level)",
8176
+ exportJsonKey: { title: "$", level: "$level" }
6292
8177
  },
6293
8178
  {
6294
8179
  key: ConfigKey.property_toc,
@@ -6319,8 +8204,31 @@ var BITS = {
6319
8204
  description: "Tags for gaps in cloze and multiple choice text bits"
6320
8205
  },
6321
8206
  {
6322
- key: ConfigKey.group_trueFalse,
8207
+ key: ConfigKey.group_trueFalseInlineSelect,
6323
8208
  description: "Tags for true/false questions in cloze and multiple choice text bits"
8209
+ },
8210
+ {
8211
+ // Bit-level @isCaseSensitive — empty exportJsonKey (no bit-level emission);
8212
+ // defaultValue 'true' phantom-fires onto the ancestor stack so the gap chain's
8213
+ // `@absent: $ancestor` rule resolves to true. Mirrors BPG Builder.ts:1643-1652
8214
+ // `pushDownTree(... 'isCaseSensitive', data.isCaseSensitive ?? true)` for cloze bits.
8215
+ key: ConfigKey.property_isCaseSensitive,
8216
+ exportJsonKey: {},
8217
+ description: "If the cloze answers are case sensitive",
8218
+ format: TagFormat.boolean,
8219
+ defaultValue: "true"
8220
+ },
8221
+ {
8222
+ // Override inherited @example: in `isTopLevelExample` allow-list.
8223
+ key: ConfigKey.property_example,
8224
+ exportJsonKey: [
8225
+ { "@keyonly": { isExample: true } },
8226
+ { "@absent": { isExample: true } },
8227
+ { isExample: true, example: "$" }
8228
+ ],
8229
+ description: "The example(s) for the bit",
8230
+ format: TagFormat.bitmarkText,
8231
+ nullable: true
6324
8232
  }
6325
8233
  ]
6326
8234
  },
@@ -6341,6 +8249,7 @@ var BITS = {
6341
8249
  },
6342
8250
  {
6343
8251
  key: ConfigKey.property_isCaseSensitive,
8252
+ exportJsonKey: {},
6344
8253
  description: "If the cloze answers are case sensitive",
6345
8254
  format: TagFormat.boolean,
6346
8255
  defaultValue: "true"
@@ -6358,18 +8267,56 @@ var BITS = {
6358
8267
  {
6359
8268
  key: ConfigKey.group_gap,
6360
8269
  description: "Tags for gaps in cloze bits"
8270
+ },
8271
+ {
8272
+ // Override inherited @example: cloze is in OLD parser's
8273
+ // `isTopLevelExample` allow-list, so [@example] at bit-header
8274
+ // emits `isExample: true`.
8275
+ key: ConfigKey.property_example,
8276
+ exportJsonKey: [
8277
+ { "@keyonly": { isExample: true } },
8278
+ { "@absent": { isExample: true } },
8279
+ { isExample: true, example: "$" }
8280
+ ],
8281
+ description: "The example(s) for the bit",
8282
+ format: TagFormat.bitmarkText,
8283
+ nullable: true
6361
8284
  }
6362
8285
  ]
6363
8286
  },
6364
8287
  [BitType2.clozeInstructionGrouped]: {
6365
8288
  since: "1.3.0",
6366
8289
  baseBitType: BitType2.cloze,
6367
- description: "Cloze instruction grouped bit, used for cloze questions with grouped instructions"
8290
+ description: "Cloze instruction grouped bit, used for cloze questions with grouped instructions",
8291
+ tags: [
8292
+ {
8293
+ key: ConfigKey.property_quizCountItems,
8294
+ description: "The number of items in the cloze quiz (instruction-grouped default: true)",
8295
+ format: TagFormat.boolean,
8296
+ defaultValue: "true"
8297
+ }
8298
+ // Note: quizStrikethroughSolutions defaults to false for instruction-grouped per OLD
8299
+ // JsonGenerator.ts:1689-1690, which equals the Boolean natural default → no override needed.
8300
+ ]
6368
8301
  },
6369
8302
  [BitType2.clozeSolutionGrouped]: {
6370
8303
  since: "1.3.0",
6371
8304
  baseBitType: BitType2.cloze,
6372
- description: "Cloze solution grouped bit, used for cloze questions with grouped solutions"
8305
+ description: "Cloze solution grouped bit, used for cloze questions with grouped solutions",
8306
+ tags: [
8307
+ {
8308
+ key: ConfigKey.property_quizCountItems,
8309
+ description: "The number of items in the cloze quiz (solution-grouped default: true)",
8310
+ format: TagFormat.boolean,
8311
+ defaultValue: "true"
8312
+ },
8313
+ {
8314
+ key: ConfigKey.property_quizStrikethroughSolutions,
8315
+ description: "If the cloze solutions should be strikethrough (solution-grouped default: true)",
8316
+ format: TagFormat.boolean,
8317
+ defaultValue: "true"
8318
+ }
8319
+ ]
6373
8320
  },
6374
8321
  [BitType2.clozeSeveral]: {
6375
8322
  since: "3.5.0",
@@ -6405,6 +8352,17 @@ var BITS = {
6405
8352
  {
6406
8353
  key: ConfigKey.group_quizCommon,
6407
8354
  description: "Common tags for quiz bits"
8355
+ },
8356
+ {
8357
+ // PLAN-084 (R12): bit-level @isCaseSensitive — empty exportJsonKey
8358
+ // (no bit-level emission); defaultValue 'true' phantom-fires onto
8359
+ // the ancestor stack so each listItem's gap chain `@absent: $ancestor`
8360
+ // rule resolves to true. Mirrors the cloze bit (bits.ts:1326-1336).
8361
+ key: ConfigKey.property_isCaseSensitive,
8362
+ exportJsonKey: {},
8363
+ description: "If the cloze answers are case sensitive",
8364
+ format: TagFormat.boolean,
8365
+ defaultValue: "true"
6408
8366
  }
6409
8367
  ],
6410
8368
  cardSet: CardSetConfigKey.clozeList
@@ -6438,6 +8396,11 @@ var BITS = {
6438
8396
  description: "Formula bit, used for mathematical formulas in articles or documents",
6439
8397
  textFormatDefault: TextFormat2.latex
6440
8398
  },
8399
+ [BitType2.formulaImage]: {
8400
+ since: "5.23.0",
8401
+ baseBitType: BitType2.formula,
8402
+ description: "Formula image bit, used for mathematical formulas rendered as images"
8403
+ },
6441
8404
  [BitType2.smartStandardFormula]: {
6442
8405
  since: "3.11.0",
6443
8406
  baseBitType: BitType2.formula,
@@ -6566,6 +8529,7 @@ var BITS = {
6566
8529
  tags: [
6567
8530
  {
6568
8531
  key: ConfigKey.group_person,
8532
+ exportJsonKey: { partner: { name: "$" } },
6569
8533
  description: "Tags for the person in the conversation"
6570
8534
  }
6571
8535
  ]
@@ -6790,6 +8754,7 @@ var BITS = {
6790
8754
  {
6791
8755
  key: ConfigKey.property_servings,
6792
8756
  jsonKey: "servings.servings",
8757
+ exportJsonKey: { servings: { servings: "$" } },
6793
8758
  description: "The number of servings for the ingredients",
6794
8759
  format: TagFormat.number,
6795
8760
  chain: [
@@ -6815,6 +8780,7 @@ var BITS = {
6815
8780
  format: TagFormat.boolean
6816
8781
  },
6817
8782
  {
8783
+ exportJsonKey: { hint: "$" },
6818
8784
  key: ConfigKey.tag_hint,
6819
8785
  description: "Hint for the ingredient, used to provide additional information",
6820
8786
  format: TagFormat.plainText
@@ -6992,6 +8958,18 @@ var BITS = {
6992
8958
  description: "Reference for the essay, used to link to external resources",
6993
8959
  format: TagFormat.plainText,
6994
8960
  maxCount: Count.infinity
8961
+ },
8962
+ {
8963
+ // Override inherited @example from group_standardTags: string root example.
8964
+ key: ConfigKey.property_example,
8965
+ exportJsonKey: [
8966
+ { "@keyonly": { isExample: true, example: "true" } },
8967
+ { "@absent": { isExample: true, example: "true" } },
8968
+ { isExample: true, example: "$" }
8969
+ ],
8970
+ description: "The example text for the essay bit",
8971
+ format: TagFormat.plainText,
8972
+ nullable: true
6995
8973
  }
6996
8974
  ],
6997
8975
  rootExampleType: ExampleType.string
@@ -7012,8 +8990,21 @@ var BITS = {
7012
8990
  description: "Example bit, used to provide examples in articles or books",
7013
8991
  tags: [
7014
8992
  {
8993
+ exportJsonKey: { title: "$" },
7015
8994
  key: ConfigKey.tag_title,
7016
8995
  description: "The title of the example"
8996
+ },
8997
+ {
8998
+ // Override inherited @example from group_standardTags: string root example.
8999
+ key: ConfigKey.property_example,
9000
+ exportJsonKey: [
9001
+ { "@keyonly": { isExample: true, example: "true" } },
9002
+ { "@absent": { isExample: true, example: "true" } },
9003
+ { isExample: true, example: "$" }
9004
+ ],
9005
+ description: "The example text for the example bit",
9006
+ format: TagFormat.plainText,
9007
+ nullable: true
7017
9008
  }
7018
9009
  ],
7019
9010
  rootExampleType: ExampleType.string
@@ -7099,7 +9090,19 @@ var BITS = {
7099
9090
  [BitType2.articleAi]: {
7100
9091
  since: "1.3.0",
7101
9092
  baseBitType: BitType2.article,
7102
- description: "Article AI bit, used to create AI-generated articles"
9093
+ description: "Article AI bit, used to create AI-generated articles",
9094
+ tags: [
9095
+ {
9096
+ // Shadows the inherited @aiGenerated from the standard group: this
9097
+ // bit type forces aiGenerated:true regardless of source value or
9098
+ // absence. Literal `true` in the export pattern ignores `$`.
9099
+ key: ConfigKey.property_aiGenerated,
9100
+ description: "AI-generated flag, forced true for article-ai bits",
9101
+ format: TagFormat.boolean,
9102
+ defaultValue: "true",
9103
+ exportJsonKey: { aiGenerated: true }
9104
+ }
9105
+ ]
7103
9106
  },
7104
9107
  [BitType2.articleAttachment]: {
7105
9108
  since: "1.3.0",
@@ -7424,7 +9427,19 @@ var BITS = {
7424
9427
  [BitType2.noteAi]: {
7425
9428
  since: "1.3.0",
7426
9429
  baseBitType: BitType2.note,
7427
- description: "Note AI bit, used to create AI-generated notes in articles or books"
9430
+ description: "Note AI bit, used to create AI-generated notes in articles or books",
9431
+ tags: [
9432
+ {
9433
+ // Shadows the inherited @aiGenerated from the standard group: this
9434
+ // bit type forces aiGenerated:true regardless of source value or
9435
+ // absence. Literal `true` in the export pattern ignores `$`.
9436
+ key: ConfigKey.property_aiGenerated,
9437
+ description: "AI-generated flag, forced true for article-ai bits",
9438
+ format: TagFormat.boolean,
9439
+ defaultValue: "true",
9440
+ exportJsonKey: { aiGenerated: true }
9441
+ }
9442
+ ]
7428
9443
  },
7429
9444
  [BitType2.notebookArticle]: {
7430
9445
  since: "1.3.0",
@@ -7549,7 +9564,19 @@ var BITS = {
7549
9564
  [BitType2.summaryAi]: {
7550
9565
  since: "1.3.0",
7551
9566
  baseBitType: BitType2.summary,
7552
- description: "AI-generated summary bit"
9567
+ description: "AI-generated summary bit",
9568
+ tags: [
9569
+ {
9570
+ // Shadows the inherited @aiGenerated from the standard group: this
9571
+ // bit type forces aiGenerated:true regardless of source value or
9572
+ // absence. Literal `true` in the export pattern ignores `$`.
9573
+ key: ConfigKey.property_aiGenerated,
9574
+ description: "AI-generated flag, forced true for article-ai bits",
9575
+ format: TagFormat.boolean,
9576
+ defaultValue: "true",
9577
+ exportJsonKey: { aiGenerated: true }
9578
+ }
9579
+ ]
7553
9580
  },
7554
9581
  [BitType2.videoTranscript]: {
7555
9582
  since: "1.3.0",
@@ -7823,7 +9850,7 @@ var BITS = {
7823
9850
  description: "Product ID for the module product, used to link to a specific product",
7824
9851
  format: TagFormat.plainText,
7825
9852
  minCount: 1,
7826
- maxCount: Count.infinity
9853
+ maxCount: 1
7827
9854
  }
7828
9855
  ]
7829
9856
  },
@@ -7876,9 +9903,13 @@ var BITS = {
7876
9903
  description: "Resource bit tags for logo grave images, used to define additional properties"
7877
9904
  },
7878
9905
  {
7879
- // Image resource
7880
- key: ConfigKey.group_resourceImage,
9906
+ // Image resource — fully-expanded shape so each `&image` emission
9907
+ // produces a complete `{type, image: {src}}` object inside the
9908
+ // `images[]` array. Chain attrs like `[@zoomDisabled]` fold under
9909
+ // `images[].image` via the `[]` last-element-merge semantic.
9910
+ key: ConfigKey.group_resourceImageNoZoom,
7881
9911
  description: "Resource image tags for logo grave images, used to attach images",
9912
+ exportJsonKey: { images: [{ type: "image", image: { src: "$" } }] },
7882
9913
  minCount: 1,
7883
9914
  maxCount: Count.infinity
7884
9915
  }
@@ -8042,15 +10073,27 @@ var BITS = {
8042
10073
  format: TagFormat.plainText,
8043
10074
  chain: [
8044
10075
  {
10076
+ exportJsonKey: { reference: "$" },
8045
10077
  key: ConfigKey.tag_reference,
8046
10078
  format: TagFormat.plainText,
8047
10079
  description: "Reference tag for the book, used to link to the book in the system",
8048
- maxCount: 2
10080
+ maxCount: 1,
10081
+ chain: [
10082
+ {
10083
+ key: ConfigKey.tag_reference,
10084
+ jsonKey: "referenceEnd",
10085
+ exportJsonKey: { referenceEnd: "$" },
10086
+ format: TagFormat.plainText,
10087
+ description: "End reference tag for the book, used to specify a range",
10088
+ maxCount: 1
10089
+ }
10090
+ ]
8049
10091
  }
8050
10092
  ]
8051
10093
  },
8052
10094
  {
8053
10095
  /* Allow incorrectly chained reference tag */
10096
+ exportJsonKey: { reference: "$" },
8054
10097
  key: ConfigKey.tag_reference,
8055
10098
  format: TagFormat.plainText,
8056
10099
  description: "Reference tag for the book, used to link to the book in the system"
@@ -8097,18 +10140,41 @@ var BITS = {
8097
10140
  format: TagFormat.plainText,
8098
10141
  chain: [
8099
10142
  {
10143
+ exportJsonKey: { reference: "$" },
8100
10144
  key: ConfigKey.tag_reference,
8101
10145
  format: TagFormat.plainText,
8102
10146
  description: "Reference tag for the book, used to link to the book in the system",
8103
- maxCount: 2
10147
+ maxCount: 1,
10148
+ chain: [
10149
+ {
10150
+ key: ConfigKey.tag_reference,
10151
+ jsonKey: "referenceEnd",
10152
+ exportJsonKey: { referenceEnd: "$" },
10153
+ format: TagFormat.plainText,
10154
+ description: "End reference tag for the book, used to specify a range",
10155
+ maxCount: 1
10156
+ }
10157
+ ]
8104
10158
  }
8105
10159
  ]
8106
10160
  },
8107
10161
  {
8108
10162
  /* Allow incorrectly chained reference tag */
10163
+ exportJsonKey: { reference: "$" },
8109
10164
  key: ConfigKey.tag_reference,
8110
10165
  format: TagFormat.plainText,
8111
- description: "Reference tag for the book, used to link to the book in the system"
10166
+ description: "Reference tag for the book, used to link to the book in the system",
10167
+ maxCount: 1,
10168
+ chain: [
10169
+ {
10170
+ key: ConfigKey.tag_reference,
10171
+ jsonKey: "referenceEnd",
10172
+ exportJsonKey: { referenceEnd: "$" },
10173
+ format: TagFormat.plainText,
10174
+ description: "End reference tag for the book, used to specify a range",
10175
+ maxCount: 1
10176
+ }
10177
+ ]
8112
10178
  },
8113
10179
  {
8114
10180
  key: ConfigKey.property_buttonCaption,
@@ -8133,14 +10199,29 @@ var BITS = {
8133
10199
  tags: [
8134
10200
  {
8135
10201
  key: ConfigKey.property_book,
10202
+ // Starter-array shape: each `[@book:value]` chain appends a fresh
10203
+ // `{ book, reference }` entry. The chained `►` (tag_reference) lands
10204
+ // inside the most-recently-appended entry as `reference: $`.
10205
+ exportJsonKey: { book: [{ book: "$" }] },
8136
10206
  description: "Book reference for the page, used to link to a specific book",
8137
10207
  maxCount: Count.infinity,
8138
10208
  chain: [
8139
10209
  {
10210
+ exportJsonKey: { reference: "$" },
8140
10211
  key: ConfigKey.tag_reference,
8141
10212
  format: TagFormat.plainText,
8142
10213
  description: "Reference tag for the book, used to link to the book in the system",
8143
- maxCount: 2
10214
+ maxCount: 1,
10215
+ chain: [
10216
+ {
10217
+ key: ConfigKey.tag_reference,
10218
+ jsonKey: "referenceEnd",
10219
+ exportJsonKey: { referenceEnd: "$" },
10220
+ format: TagFormat.plainText,
10221
+ description: "End reference tag for the book, used to specify a range",
10222
+ maxCount: 1
10223
+ }
10224
+ ]
8144
10225
  }
8145
10226
  ]
8146
10227
  },
@@ -8189,7 +10270,9 @@ var BITS = {
8189
10270
  format: TagFormat.plainText
8190
10271
  }
8191
10272
  ],
8192
- cardSet: CardSetConfigKey.exampleBitList
10273
+ // page-footer emits cards under `sections` (vs `listItems` for the
10274
+ // sibling exampleList / assignmentList bits) — see JsonGenerator.ts.
10275
+ cardSet: CardSetConfigKey.pageFooterSections
8193
10276
  },
8194
10277
  [BitType2.legend]: {
8195
10278
  since: "3.12.0",
@@ -8231,6 +10314,20 @@ var BITS = {
8231
10314
  since: "1.34.0",
8232
10315
  baseBitType: BitType2._standard,
8233
10316
  description: "Definition list bit, used to create lists of definitions in articles or books",
10317
+ tags: [
10318
+ {
10319
+ // Override inherited @example: in `isTopLevelExample` allow-list.
10320
+ key: ConfigKey.property_example,
10321
+ exportJsonKey: [
10322
+ { "@keyonly": { isExample: true } },
10323
+ { "@absent": { isExample: true } },
10324
+ { isExample: true, example: "$" }
10325
+ ],
10326
+ description: "The example(s) for the bit",
10327
+ format: TagFormat.bitmarkText,
10328
+ nullable: true
10329
+ }
10330
+ ],
8234
10331
  cardSet: CardSetConfigKey.definitionList
8235
10332
  },
8236
10333
  [BitType2.metaSearchDefaultTerms]: {
@@ -8253,6 +10350,18 @@ var BITS = {
8253
10350
  {
8254
10351
  key: ConfigKey.group_quizCommon,
8255
10352
  description: "Common quiz tags for flashcards"
10353
+ },
10354
+ {
10355
+ // Override inherited @example: in `isTopLevelExample` allow-list.
10356
+ key: ConfigKey.property_example,
10357
+ exportJsonKey: [
10358
+ { "@keyonly": { isExample: true } },
10359
+ { "@absent": { isExample: true } },
10360
+ { isExample: true, example: "$" }
10361
+ ],
10362
+ description: "The example(s) for the bit",
10363
+ format: TagFormat.bitmarkText,
10364
+ nullable: true
8256
10365
  }
8257
10366
  ],
8258
10367
  cardSet: CardSetConfigKey.flashcard
@@ -8260,7 +10369,11 @@ var BITS = {
8260
10369
  [BitType2.flashcard1]: {
8261
10370
  since: "1.3.0",
8262
10371
  baseBitType: BitType2.flashcard,
8263
- description: "Flashcard 1 bit"
10372
+ description: "Flashcard 1 bit",
10373
+ // PLAN-085: use the flashcard1 cardset (sections.default.maxCount=1)
10374
+ // instead of inheriting `flashcard`'s unbounded cardset, since this
10375
+ // bit type structurally allows only a single card.
10376
+ cardSet: CardSetConfigKey.flashcard1
8264
10377
  },
8265
10378
  [BitType2.qAndACard]: {
8266
10379
  since: "3.25.0",
@@ -8295,8 +10408,24 @@ var BITS = {
8295
10408
  description: "Common quiz tags for highlighted text"
8296
10409
  },
8297
10410
  {
8298
- key: ConfigKey.group_trueFalse,
8299
- description: "True/False quiz tags for highlighted text"
10411
+ // Dedicated highlight inline-select chain: tag_true / tag_false
10412
+ // emit `{type:"highlight", attrs:{texts:[{text,isCorrect?}]}}`
10413
+ // inline body nodes via @body-inline. Bit shares no tag IDs with
10414
+ // multiple-choice / multiple-choice-1 (which use group_trueFalse).
10415
+ key: ConfigKey.group_trueFalseInlineHighlight,
10416
+ description: "True/False quiz tags for highlighted text (inline body nodes)"
10417
+ },
10418
+ {
10419
+ // Override inherited @example: in `isTopLevelExample` allow-list.
10420
+ key: ConfigKey.property_example,
10421
+ exportJsonKey: [
10422
+ { "@keyonly": { isExample: true } },
10423
+ { "@absent": { isExample: true } },
10424
+ { isExample: true, example: "$" }
10425
+ ],
10426
+ description: "The example(s) for the bit",
10427
+ format: TagFormat.bitmarkText,
10428
+ nullable: true
8300
10429
  }
8301
10430
  ]
8302
10431
  },
@@ -8306,14 +10435,8 @@ var BITS = {
8306
10435
  description: "Image bit, used to create images in articles or books",
8307
10436
  tags: [
8308
10437
  {
8309
- key: ConfigKey.resource_backgroundWallpaper,
8310
- description: "Background wallpaper for the image, used to set a background for the image",
8311
- chain: [
8312
- {
8313
- key: ConfigKey.group_resourceImageCommon,
8314
- description: "Common resource image tags for images"
8315
- }
8316
- ]
10438
+ key: ConfigKey.group_backgroundWallpaper,
10439
+ description: "Background wallpaper tags for images, used to define background properties for images"
8317
10440
  },
8318
10441
  {
8319
10442
  key: ConfigKey.group_resourceBitTags,
@@ -8416,8 +10539,15 @@ var BITS = {
8416
10539
  },
8417
10540
  [BitType2.imageSeparator]: {
8418
10541
  since: "1.4.15",
8419
- baseBitType: BitType2.image,
8420
- description: "Image separator bit, used to create separators in articles or books"
10542
+ baseBitType: BitType2._standard,
10543
+ description: "Image separator bit, used to create separators in articles or books",
10544
+ tags: [
10545
+ {
10546
+ key: ConfigKey.group_imageNoZoom,
10547
+ description: "Image no zoom tags for images, used to define whether images should have zoom functionality"
10548
+ }
10549
+ ],
10550
+ resourceAttachmentAllowed: false
8421
10551
  },
8422
10552
  [BitType2.imageSeparatorAlt]: {
8423
10553
  since: "1.16.0",
@@ -8456,15 +10586,20 @@ var BITS = {
8456
10586
  },
8457
10587
  [BitType2.pageBanner]: {
8458
10588
  since: "1.4.3",
8459
- baseBitType: BitType2.image,
10589
+ baseBitType: BitType2._standard,
8460
10590
  description: "Page banner bit, used to create banners for pages in articles or books",
8461
10591
  tags: [
8462
10592
  {
8463
10593
  key: ConfigKey.property_slug,
8464
10594
  description: "Slug for the page banner, used to identify the banner in the system",
8465
10595
  format: TagFormat.plainText
10596
+ },
10597
+ {
10598
+ key: ConfigKey.group_imageNoZoom,
10599
+ description: "Image no zoom tags for images, used to define whether images should have zoom functionality"
8466
10600
  }
8467
- ]
10601
+ ],
10602
+ resourceAttachmentAllowed: false
8468
10603
  },
8469
10604
  [BitType2.pageHero]: {
8470
10605
  since: "1.11.0",
@@ -8479,7 +10614,7 @@ var BITS = {
8479
10614
  },
8480
10615
  [BitType2.tableImage]: {
8481
10616
  since: "1.5.15",
8482
- baseBitType: BitType2.table,
10617
+ baseBitType: BitType2.tableExtended,
8483
10618
  description: "Table image bit, used to create images in tables in articles or books",
8484
10619
  tags: [
8485
10620
  {
@@ -8488,14 +10623,8 @@ var BITS = {
8488
10623
  format: TagFormat.bitmarkText
8489
10624
  },
8490
10625
  {
8491
- key: ConfigKey.resource_backgroundWallpaper,
8492
- description: "Background wallpaper for the image, used to set a background for the image",
8493
- chain: [
8494
- {
8495
- key: ConfigKey.group_resourceImageCommon,
8496
- description: "Common resource image tags for images"
8497
- }
8498
- ]
10626
+ key: ConfigKey.group_backgroundWallpaper,
10627
+ description: "Background wallpaper tags for images, used to define background properties for images"
8499
10628
  },
8500
10629
  {
8501
10630
  key: ConfigKey.group_resourceBitTags,
@@ -8638,9 +10767,10 @@ var BITS = {
8638
10767
  description: "Resource bit tags for logo grave images, used to define additional properties"
8639
10768
  },
8640
10769
  {
8641
- // Image resource
8642
- key: ConfigKey.group_resourceImage,
10770
+ // Image resource — fully-expanded shape (see BitType.extractorImage).
10771
+ key: ConfigKey.group_resourceImageNoZoom,
8643
10772
  description: "Resource image tags for logo grave images, used to attach images",
10773
+ exportJsonKey: { logos: [{ type: "image", image: { src: "$" } }] },
8644
10774
  minCount: 1,
8645
10775
  maxCount: Count.infinity
8646
10776
  }
@@ -8650,7 +10780,17 @@ var BITS = {
8650
10780
  [BitType2.prototypeImages]: {
8651
10781
  since: "1.6.1",
8652
10782
  baseBitType: BitType2.imagesLogoGrave,
8653
- description: "Prototype images bit, used to create prototype images in articles or books"
10783
+ description: "Prototype images bit, used to create prototype images in articles or books",
10784
+ tags: [
10785
+ {
10786
+ // Override inherited `logos` wrapper with `images` for this bit type.
10787
+ key: ConfigKey.group_resourceImageNoZoom,
10788
+ description: "Resource image tags for prototype images",
10789
+ exportJsonKey: { images: [{ type: "image", image: { src: "$" } }] },
10790
+ minCount: 1,
10791
+ maxCount: Count.infinity
10792
+ }
10793
+ ]
8654
10794
  },
8655
10795
  [BitType2.internalLink]: {
8656
10796
  since: "1.3.0",
@@ -8658,6 +10798,7 @@ var BITS = {
8658
10798
  description: "Internal link bit, used to create links to other bits in articles or books",
8659
10799
  tags: [
8660
10800
  {
10801
+ exportJsonKey: { reference: "$" },
8661
10802
  key: ConfigKey.tag_reference,
8662
10803
  format: TagFormat.plainText,
8663
10804
  description: "Reference tag for the internal link, used to link to the target bit"
@@ -8676,8 +10817,21 @@ var BITS = {
8676
10817
  },
8677
10818
  {
8678
10819
  key: ConfigKey.property_reasonableNumOfChars,
10820
+ exportJsonKey: {},
8679
10821
  description: "Reasonable number of characters for the interview, used to limit input size",
8680
10822
  format: TagFormat.number
10823
+ },
10824
+ {
10825
+ // Override inherited @example: in `isTopLevelExample` allow-list.
10826
+ key: ConfigKey.property_example,
10827
+ exportJsonKey: [
10828
+ { "@keyonly": { isExample: true } },
10829
+ { "@absent": { isExample: true } },
10830
+ { isExample: true, example: "$" }
10831
+ ],
10832
+ description: "The example(s) for the bit",
10833
+ format: TagFormat.bitmarkText,
10834
+ nullable: true
8681
10835
  }
8682
10836
  ],
8683
10837
  cardSet: CardSetConfigKey.questions
@@ -9036,6 +11190,18 @@ var BITS = {
9036
11190
  {
9037
11191
  key: ConfigKey.group_mark,
9038
11192
  description: "Mark tags, used to define the content of marks"
11193
+ },
11194
+ {
11195
+ // Override inherited @example: in `isTopLevelExample` allow-list.
11196
+ key: ConfigKey.property_example,
11197
+ exportJsonKey: [
11198
+ { "@keyonly": { isExample: true } },
11199
+ { "@absent": { isExample: true } },
11200
+ { isExample: true, example: "$" }
11201
+ ],
11202
+ description: "The example(s) for the bit",
11203
+ format: TagFormat.bitmarkText,
11204
+ nullable: true
9039
11205
  }
9040
11206
  ]
9041
11207
  },
@@ -9051,9 +11217,27 @@ var BITS = {
9051
11217
  },
9052
11218
  {
9053
11219
  key: ConfigKey.property_isCaseSensitive,
11220
+ exportJsonKey: {},
9054
11221
  description: "Case sensitivity for matching pairs, used to define if matches are case-sensitive",
9055
11222
  format: TagFormat.boolean,
9056
11223
  defaultValue: "true"
11224
+ },
11225
+ {
11226
+ // Override inherited @example: in `isTopLevelExample` allow-list
11227
+ // (covers match, matchAll, matchReverse, matchAllReverse,
11228
+ // matchSolutionGrouped, matchMatrix, matchAudio, matchPicture).
11229
+ // Bit-level fires emit only `isExample`; the @example value (when
11230
+ // provided) is recorded in the ancestor stack and consumed by
11231
+ // per-pair/per-cell variant overrides — not surfaced at bit scope.
11232
+ key: ConfigKey.property_example,
11233
+ exportJsonKey: [
11234
+ { "@keyonly": { isExample: true } },
11235
+ { "@absent": { isExample: true } },
11236
+ { isExample: true }
11237
+ ],
11238
+ description: "The example(s) for the bit",
11239
+ format: TagFormat.bitmarkText,
11240
+ nullable: true
9057
11241
  }
9058
11242
  ],
9059
11243
  cardSet: CardSetConfigKey.matchPairs
@@ -9103,6 +11287,7 @@ var BITS = {
9103
11287
  tags: [
9104
11288
  {
9105
11289
  key: ConfigKey.property_reasonableNumOfChars,
11290
+ exportJsonKey: {},
9106
11291
  description: "Reasonable number of characters for feedback, used to limit input size",
9107
11292
  format: TagFormat.number
9108
11293
  }
@@ -9137,6 +11322,18 @@ var BITS = {
9137
11322
  {
9138
11323
  key: ConfigKey.group_trueFalse,
9139
11324
  description: "True/False tags for multiple choice 1 questions"
11325
+ },
11326
+ {
11327
+ // Override inherited @example: in `isTopLevelExample` allow-list.
11328
+ key: ConfigKey.property_example,
11329
+ exportJsonKey: [
11330
+ { "@keyonly": { isExample: true } },
11331
+ { "@absent": { isExample: true } },
11332
+ { isExample: true, example: "$" }
11333
+ ],
11334
+ description: "The example(s) for the bit",
11335
+ format: TagFormat.bitmarkText,
11336
+ nullable: true
9140
11337
  }
9141
11338
  ]
9142
11339
  },
@@ -9158,8 +11355,20 @@ var BITS = {
9158
11355
  {
9159
11356
  key: ConfigKey.group_trueFalse,
9160
11357
  description: "True/False tags for multiple choice questions"
9161
- }
11358
+ },
9162
11359
  // This is actually for multiple-choice-1, but we support it here as well (as many bits are wrong)
11360
+ {
11361
+ // Override inherited @example: in `isTopLevelExample` allow-list.
11362
+ key: ConfigKey.property_example,
11363
+ exportJsonKey: [
11364
+ { "@keyonly": { isExample: true } },
11365
+ { "@absent": { isExample: true } },
11366
+ { isExample: true, example: "$" }
11367
+ ],
11368
+ description: "The example(s) for the bit",
11369
+ format: TagFormat.bitmarkText,
11370
+ nullable: true
11371
+ }
9163
11372
  ],
9164
11373
  cardSet: CardSetConfigKey.quiz
9165
11374
  },
@@ -9179,8 +11388,20 @@ var BITS = {
9179
11388
  description: "Common quiz tags for multiple choice text questions"
9180
11389
  },
9181
11390
  {
9182
- key: ConfigKey.group_trueFalse,
11391
+ key: ConfigKey.group_trueFalseInlineSelect,
9183
11392
  description: "True/False tags for multiple choice text questions"
11393
+ },
11394
+ {
11395
+ // Override inherited @example: in `isTopLevelExample` allow-list.
11396
+ key: ConfigKey.property_example,
11397
+ exportJsonKey: [
11398
+ { "@keyonly": { isExample: true } },
11399
+ { "@absent": { isExample: true } },
11400
+ { isExample: true, example: "$" }
11401
+ ],
11402
+ description: "The example(s) for the bit",
11403
+ format: TagFormat.bitmarkText,
11404
+ nullable: true
9184
11405
  }
9185
11406
  ]
9186
11407
  },
@@ -9205,8 +11426,20 @@ var BITS = {
9205
11426
  description: "Common quiz tags for multiple response 1 questions"
9206
11427
  },
9207
11428
  {
9208
- key: ConfigKey.group_trueFalse,
11429
+ key: ConfigKey.group_trueFalseResponses,
9209
11430
  description: "True/False tags for multiple response 1 questions"
11431
+ },
11432
+ {
11433
+ // Override inherited @example: in `isTopLevelExample` allow-list.
11434
+ key: ConfigKey.property_example,
11435
+ exportJsonKey: [
11436
+ { "@keyonly": { isExample: true } },
11437
+ { "@absent": { isExample: true } },
11438
+ { isExample: true, example: "$" }
11439
+ ],
11440
+ description: "The example(s) for the bit",
11441
+ format: TagFormat.bitmarkText,
11442
+ nullable: true
9210
11443
  }
9211
11444
  ]
9212
11445
  },
@@ -9226,12 +11459,24 @@ var BITS = {
9226
11459
  description: "Common quiz tags for multiple response questions"
9227
11460
  },
9228
11461
  {
9229
- key: ConfigKey.group_trueFalse,
11462
+ key: ConfigKey.group_trueFalseResponses,
9230
11463
  description: "True/False tags for multiple response questions"
9231
- }
11464
+ },
9232
11465
  // This is actually for multiple-response-1, but we support it here as well (as many bits are wrong)
11466
+ {
11467
+ // Override inherited @example: in `isTopLevelExample` allow-list.
11468
+ key: ConfigKey.property_example,
11469
+ exportJsonKey: [
11470
+ { "@keyonly": { isExample: true } },
11471
+ { "@absent": { isExample: true } },
11472
+ { isExample: true, example: "$" }
11473
+ ],
11474
+ description: "The example(s) for the bit",
11475
+ format: TagFormat.bitmarkText,
11476
+ nullable: true
11477
+ }
9233
11478
  ],
9234
- cardSet: CardSetConfigKey.quiz
11479
+ cardSet: CardSetConfigKey.quizResponses
9235
11480
  },
9236
11481
  [BitType2.coachSelfReflectionMultipleResponse]: {
9237
11482
  since: "1.3.0",
@@ -9244,6 +11489,7 @@ var BITS = {
9244
11489
  description: "Page bit, used to create pages in articles or books",
9245
11490
  tags: [
9246
11491
  {
11492
+ exportJsonKey: { title: "$" },
9247
11493
  key: ConfigKey.tag_title,
9248
11494
  description: "Title of the page, used to display the page title"
9249
11495
  },
@@ -9480,6 +11726,8 @@ var BITS = {
9480
11726
  },
9481
11727
  {
9482
11728
  key: ConfigKey.resource_platformIcon,
11729
+ jsonKey: "platformIcon|resource(type=image, key=image)",
11730
+ exportJsonKey: { platformIcon: { type: "image", image: { src: "$" } } },
9483
11731
  description: "The platform icon",
9484
11732
  chain: [
9485
11733
  {
@@ -9515,6 +11763,8 @@ var BITS = {
9515
11763
  tags: [
9516
11764
  {
9517
11765
  key: ConfigKey.resource_platformLogo,
11766
+ jsonKey: "platformLogo|resource(type=image, key=image)",
11767
+ exportJsonKey: { platformLogo: { type: "image", image: { src: "$" } } },
9518
11768
  description: "The platform logo",
9519
11769
  chain: [
9520
11770
  {
@@ -9603,6 +11853,7 @@ var BITS = {
9603
11853
  {
9604
11854
  key: ConfigKey.resource_platformBackgroundImage,
9605
11855
  jsonKey: "platformBackgroundImage|resource(type=image, key=image)",
11856
+ exportJsonKey: { platformBackgroundImage: { type: "image", image: { src: "$" } } },
9606
11857
  description: "The platform section chat background image",
9607
11858
  chain: [
9608
11859
  {
@@ -9696,6 +11947,7 @@ var BITS = {
9696
11947
  description: "Review note bit, used to create review notes in articles or books",
9697
11948
  tags: [
9698
11949
  {
11950
+ exportJsonKey: { title: "$" },
9699
11951
  key: ConfigKey.tag_title,
9700
11952
  description: "Title of the review note, used to display the note title"
9701
11953
  },
@@ -9714,6 +11966,18 @@ var BITS = {
9714
11966
  key: ConfigKey.property_resolvedBy,
9715
11967
  description: "Resolved by for the review note, used to define who resolved the note",
9716
11968
  format: TagFormat.plainText
11969
+ },
11970
+ {
11971
+ // Override inherited @example from group_standardTags: string root example.
11972
+ key: ConfigKey.property_example,
11973
+ exportJsonKey: [
11974
+ { "@keyonly": { isExample: true, example: "true" } },
11975
+ { "@absent": { isExample: true, example: "true" } },
11976
+ { isExample: true, example: "$" }
11977
+ ],
11978
+ description: "The example text for the review note bit",
11979
+ format: TagFormat.plainText,
11980
+ nullable: true
9717
11981
  }
9718
11982
  ],
9719
11983
  rootExampleType: ExampleType.string
@@ -9766,11 +12030,13 @@ var BITS = {
9766
12030
  tags: [
9767
12031
  // Not sure if these are actually valid here, but include them as they are in the test bit.
9768
12032
  {
12033
+ exportJsonKey: { anchor: "$" },
9769
12034
  key: ConfigKey.tag_anchor,
9770
12035
  format: TagFormat.plainText,
9771
12036
  description: "Anchor for the sample solution, used to link to the solution"
9772
12037
  },
9773
12038
  {
12039
+ exportJsonKey: { reference: "$" },
9774
12040
  key: ConfigKey.tag_reference,
9775
12041
  format: TagFormat.plainText,
9776
12042
  description: "Reference for the sample solution, used to link to the source of the solution"
@@ -9786,6 +12052,20 @@ var BITS = {
9786
12052
  {
9787
12053
  key: ConfigKey.group_quizCommon,
9788
12054
  description: "Common quiz tags for sequences"
12055
+ },
12056
+ {
12057
+ // Override inherited @example from group_standardTags: this bit has
12058
+ // a boolean root example (rootExampleType: boolean), so bit-level
12059
+ // [@example] writes `example: true` in addition to `isExample: true`.
12060
+ key: ConfigKey.property_example,
12061
+ exportJsonKey: [
12062
+ { "@keyonly": { isExample: true, example: true } },
12063
+ { "@absent": { isExample: true, example: true } },
12064
+ { isExample: true, example: "$" }
12065
+ ],
12066
+ description: "The example flag for the bit (boolean)",
12067
+ format: TagFormat.boolean,
12068
+ nullable: true
9789
12069
  }
9790
12070
  ],
9791
12071
  cardSet: CardSetConfigKey.elements,
@@ -9893,6 +12173,7 @@ var BITS = {
9893
12173
  {
9894
12174
  key: ConfigKey.property_ratingLevelStart,
9895
12175
  jsonKey: "ratingLevelStart.level",
12176
+ exportJsonKey: { ratingLevelStart: { level: "$" } },
9896
12177
  description: "Start level for the rating survey, used to define the lowest rating",
9897
12178
  format: TagFormat.number,
9898
12179
  chain: [
@@ -9906,6 +12187,7 @@ var BITS = {
9906
12187
  {
9907
12188
  key: ConfigKey.property_ratingLevelEnd,
9908
12189
  jsonKey: "ratingLevelEnd.level",
12190
+ exportJsonKey: { ratingLevelEnd: { level: "$" } },
9909
12191
  description: "End level for the rating survey, used to define the highest rating",
9910
12192
  format: TagFormat.number,
9911
12193
  chain: [
@@ -10057,14 +12339,8 @@ var BITS = {
10057
12339
  format: TagFormat.bitmarkText
10058
12340
  },
10059
12341
  {
10060
- key: ConfigKey.resource_backgroundWallpaper,
10061
- description: "Background wallpaper for the image, used to set a background for the image",
10062
- chain: [
10063
- {
10064
- key: ConfigKey.group_resourceImageCommon,
10065
- description: "Common resource image tags for images"
10066
- }
10067
- ]
12342
+ key: ConfigKey.group_backgroundWallpaper,
12343
+ description: "Background wallpaper tags for images, used to define background properties for images"
10068
12344
  },
10069
12345
  {
10070
12346
  key: ConfigKey.group_resourceBitTags,
@@ -10441,12 +12717,48 @@ var BITS = {
10441
12717
  format: TagFormat.plainText
10442
12718
  },
10443
12719
  {
12720
+ // PLAN-067 Edit #1: true-false-1's `+` / `-` emit a flat
12721
+ // `{ statement, isCorrect }` at bit root (single-statement bit),
12722
+ // not a `choices[]` array. Format is bitmarkText so the statement
12723
+ // body renders as a ProseMirror tree, matching reference fixtures.
12724
+ exportJsonKey: { statement: "$", isCorrect: true },
10444
12725
  key: ConfigKey.tag_true,
10445
- description: "Tag for the true option in the true/false question"
12726
+ description: "Tag for the true option in the true/false question",
12727
+ format: TagFormat.bitmarkText
10446
12728
  },
10447
12729
  {
12730
+ exportJsonKey: { statement: "$", isCorrect: false },
10448
12731
  key: ConfigKey.tag_false,
10449
- description: "Tag for the false option in the true/false question"
12732
+ description: "Tag for the false option in the true/false question",
12733
+ format: TagFormat.bitmarkText
12734
+ },
12735
+ {
12736
+ // Override inherited @example from group_standardTags: boolean root
12737
+ // example. Note: the legacy reference parser ties the bare-form
12738
+ // example value to `statement.isCorrect`, but the statement is not
12739
+ // yet emitted when this tag fires at bit-header. The pattern below
12740
+ // emits a literal `true` for bare/synthesized and `$` for valued —
12741
+ // sufficient for `[+...]` (correct) statements which dominate the
12742
+ // fixtures; `[-...]` (incorrect) bits with bare `[@example]` would
12743
+ // need either a runtime extension or an explicit `[@example:false]`.
12744
+ //
12745
+ // Format is `boolean` so `[@example:false]` coerces to JSON `false`
12746
+ // (a natural default that gets stripped in optimised mode, matching
12747
+ // BPG's `rootExampleType: boolean`). PlainText would emit the
12748
+ // STRING `"false"` which doesn't strip.
12749
+ key: ConfigKey.property_example,
12750
+ exportJsonKey: [
12751
+ { "@keyonly": { isExample: true, example: true } },
12752
+ { "@absent": { isExample: true, example: true } },
12753
+ // `[@example:false]`: emit `isExample: true` only — `example: false`
12754
+ // is the natural default for boolean, stripped in optimised mode.
12755
+ // Mirrors BPG `removeUnwantedProperties` `ignoreAllFalse: true`.
12756
+ { "@value=false": { isExample: true } },
12757
+ { isExample: true, example: "$" }
12758
+ ],
12759
+ description: "The example flag for the bit (boolean)",
12760
+ format: TagFormat.boolean,
12761
+ nullable: true
10450
12762
  }
10451
12763
  ],
10452
12764
  rootExampleType: ExampleType.boolean
@@ -10470,6 +12782,18 @@ var BITS = {
10470
12782
  key: ConfigKey.property_labelFalse,
10471
12783
  description: "Label for the false option in the true/false question",
10472
12784
  format: TagFormat.plainText
12785
+ },
12786
+ {
12787
+ // Override inherited @example: in `isTopLevelExample` allow-list.
12788
+ key: ConfigKey.property_example,
12789
+ exportJsonKey: [
12790
+ { "@keyonly": { isExample: true } },
12791
+ { "@absent": { isExample: true } },
12792
+ { isExample: true, example: "$" }
12793
+ ],
12794
+ description: "The example(s) for the bit",
12795
+ format: TagFormat.bitmarkText,
12796
+ nullable: true
10473
12797
  }
10474
12798
  ],
10475
12799
  cardSet: CardSetConfigKey.statements
@@ -10659,6 +12983,11 @@ var BITS = {
10659
12983
  baseBitType: BitType2.vendorStripePricingTable,
10660
12984
  description: "Stripe pricing table bit with print-on-request customer/product IDs, used to embed Stripe pricing tables tied to a specific print-on-request customer and product",
10661
12985
  tags: [
12986
+ {
12987
+ key: ConfigKey.property_stripePricingTableDescriptiveName,
12988
+ description: "Descriptive name for the Stripe pricing table",
12989
+ format: TagFormat.plainText
12990
+ },
10662
12991
  {
10663
12992
  key: ConfigKey.property_printOnRequestCustomerId,
10664
12993
  description: "Print-on-request customer ID, used to identify the customer for the print-on-request flow",
@@ -11029,6 +13358,8 @@ var Config = class {
11029
13358
  maxCount: 1,
11030
13359
  chain: tag.chain,
11031
13360
  jsonKey: tag.jsonKey,
13361
+ exportJsonKey: tag.exportJsonKey,
13362
+ hasExportJsonKey: tag.hasExportJsonKey,
11032
13363
  deprecated: tag.deprecated
11033
13364
  });
11034
13365
  finalResourceTags[k] = newTag;
@@ -11133,7 +13464,7 @@ var instance2 = new Config();
11133
13464
  // src/generated/package_info.ts
11134
13465
  var PACKAGE_INFO = {
11135
13466
  "name": "@gmb/bitmark-parser-generator",
11136
- "version": "5.22.0",
13467
+ "version": "5.24.0",
11137
13468
  "author": "Get More Brain Ltd",
11138
13469
  "license": "ISC",
11139
13470
  "description": "A bitmark parser and generator using Peggy.js"
@@ -11727,6 +14058,8 @@ var NodeType = {
11727
14058
  gmbExternalShopItems: "gmbExternalShopItems",
11728
14059
  groupTag: "groupTag",
11729
14060
  groupTagValue: "groupTagValue",
14061
+ gUri: "gUri",
14062
+ gUriValue: "gUriValue",
11730
14063
  handInAcceptFileType: "handInAcceptFileType",
11731
14064
  handInAcceptFileTypeValue: "handInAcceptFileTypeValue",
11732
14065
  handInInstruction: "handInInstruction",
@@ -12069,6 +14402,8 @@ var NodeType = {
12069
14402
  solution: "solution",
12070
14403
  solutions: "solutions",
12071
14404
  solutionsValue: "solutionsValue",
14405
+ sourceBB: "sourceBB",
14406
+ sourceBBValue: "sourceBBValue",
12072
14407
  sourceDocument: "sourceDocument",
12073
14408
  sourceDocumentValue: "sourceDocumentValue",
12074
14409
  sourceRL: "sourceRL",
@@ -12094,6 +14429,8 @@ var NodeType = {
12094
14429
  stillImageFilmLink: "stillImageFilmLink",
12095
14430
  stripePricingTableId: "stripePricingTableId",
12096
14431
  stripePricingTableIdValue: "stripePricingTableIdValue",
14432
+ stripePricingTableDescriptiveName: "stripePricingTableDescriptiveName",
14433
+ stripePricingTableDescriptiveNameValue: "stripePricingTableDescriptiveNameValue",
12097
14434
  stripePublishableKey: "stripePublishableKey",
12098
14435
  stripePublishableKeyValue: "stripePublishableKeyValue",
12099
14436
  subject: "subject",
@@ -24949,6 +27286,7 @@ var Builder = class extends BaseBuilder {
24949
27286
  isCommented: data.isCommented,
24950
27287
  // Properties
24951
27288
  id: this.toAstProperty(bitType, ConfigKey.property_id, data.id, options),
27289
+ gUri: this.toAstProperty(bitType, ConfigKey.property_gUri, data.gUri, options),
24952
27290
  internalComment: this.toAstProperty(
24953
27291
  bitType,
24954
27292
  ConfigKey.property_internalComment,
@@ -25160,6 +27498,7 @@ var Builder = class extends BaseBuilder {
25160
27498
  data.extractorInternal,
25161
27499
  options
25162
27500
  ),
27501
+ sourceBB: this.normaliseSourceBB(data.sourceBB),
25163
27502
  levelCEFRp: this.toAstProperty(
25164
27503
  bitType,
25165
27504
  ConfigKey.property_levelCEFRp,
@@ -25517,6 +27856,12 @@ var Builder = class extends BaseBuilder {
25517
27856
  data.stripePricingTableId,
25518
27857
  options
25519
27858
  ),
27859
+ stripePricingTableDescriptiveName: this.toAstProperty(
27860
+ bitType,
27861
+ ConfigKey.property_stripePricingTableDescriptiveName,
27862
+ data.stripePricingTableDescriptiveName,
27863
+ options
27864
+ ),
25520
27865
  stripePublishableKey: this.toAstProperty(
25521
27866
  bitType,
25522
27867
  ConfigKey.property_stripePublishableKey,
@@ -26180,6 +28525,19 @@ var Builder = class extends BaseBuilder {
26180
28525
  nodes = Object.values(combinedNodes);
26181
28526
  return nodes.length > 0 ? nodes : void 0;
26182
28527
  }
28528
+ /**
28529
+ * Normalise a sourceBB input into the AST shape (always number[][]).
28530
+ * Accepts a single 4-number tuple or an array of tuples; returns undefined when empty.
28531
+ */
28532
+ normaliseSourceBB(data) {
28533
+ if (data == null) return void 0;
28534
+ if (!Array.isArray(data) || data.length === 0) return void 0;
28535
+ const isNested = data.some(Array.isArray);
28536
+ const tuples = (isNested ? data : [data]).filter(
28537
+ (t) => Array.isArray(t) && t.length === 4 && t.every((n) => typeof n === "number")
28538
+ );
28539
+ return tuples.length > 0 ? tuples : void 0;
28540
+ }
26183
28541
  /**
26184
28542
  * Build groupTag node
26185
28543
  *
@@ -28499,6 +30857,23 @@ var BitmarkGenerator = class extends AstWalkerGenerator {
28499
30857
  format: TagFormat.plainText
28500
30858
  });
28501
30859
  }
30860
+ // bitmarkAst -> bits -> bitsValue -> sourceBB
30861
+ enter_sourceBB(node, route) {
30862
+ const parent = this.getParentNode(route);
30863
+ if (parent?.key !== NodeType.bitsValue) return false;
30864
+ const tuples = node.value;
30865
+ if (!Array.isArray(tuples) || tuples.length === 0) return false;
30866
+ this.writeProperty(
30867
+ "sourceBB",
30868
+ tuples.map((t) => t.join(", ")),
30869
+ route,
30870
+ {
30871
+ format: TagFormat.plainText,
30872
+ array: true
30873
+ }
30874
+ );
30875
+ return false;
30876
+ }
28502
30877
  // bitmarkAst -> bits -> bitsValue -> questions -> questionsValue -> additionalSolutions -> additionalSolutionsValue
28503
30878
  leaf_additionalSolutionsValue(node, route) {
28504
30879
  const parent = this.getParentNode(route, 2);
@@ -30622,6 +32997,16 @@ var JsonGenerator = class extends AstWalkerGenerator {
30622
32997
  enter_ratingLevelEnd(node, route) {
30623
32998
  return this.standardHandler(node, route, NodeType.bitsValue, { array: false });
30624
32999
  }
33000
+ // bitmarkAst -> bits -> bitsValue -> sourceBB
33001
+ enter_sourceBB(node, route) {
33002
+ const tuples = node.value;
33003
+ const parent = this.getParentNode(route);
33004
+ if (parent?.key !== NodeType.bitsValue) return false;
33005
+ if (tuples && tuples.length > 0) {
33006
+ this.bitJson.sourceBB = tuples.length === 1 ? tuples[0] : tuples;
33007
+ }
33008
+ return false;
33009
+ }
30625
33010
  // bitmarkAst -> bits -> bitsValue -> productId
30626
33011
  enter_productId(node, route) {
30627
33012
  const productIds = node.value;
@@ -33704,6 +36089,7 @@ function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, st
33704
36089
  const cardSetType = bitConfig.cardSet?.configKey;
33705
36090
  switch (cardSetType) {
33706
36091
  case CardSetConfigKey.flashcard:
36092
+ case CardSetConfigKey.flashcard1:
33707
36093
  case CardSetConfigKey.definitionList:
33708
36094
  result = parseFlashcardOrDefinitionList(context, bitType, processedCardSet);
33709
36095
  break;
@@ -33717,6 +36103,7 @@ function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, st
33717
36103
  result = parseFeedback(context, bitType, processedCardSet);
33718
36104
  break;
33719
36105
  case CardSetConfigKey.quiz:
36106
+ case CardSetConfigKey.quizResponses:
33720
36107
  result = parseQuiz(context, bitType, processedCardSet, choicesV1, responsesV1);
33721
36108
  break;
33722
36109
  case CardSetConfigKey.questions:
@@ -33743,6 +36130,7 @@ function buildCards(context, bitType, textFormat, parsedCardSet, statementV1, st
33743
36130
  break;
33744
36131
  case CardSetConfigKey.clozeList:
33745
36132
  case CardSetConfigKey.exampleBitList:
36133
+ case CardSetConfigKey.pageFooterSections:
33746
36134
  case CardSetConfigKey.bookReferenceList:
33747
36135
  result = parseCardBits(context, bitType, textFormat, processedCardSet);
33748
36136
  break;
@@ -35300,6 +37688,17 @@ function propertyContentProcessor(context, contentDepth, tagsConfig, content, ta
35300
37688
  format: TextFormat2.bitmarkText,
35301
37689
  location: TextLocation2.tag
35302
37690
  });
37691
+ case TagFormat.coordinates:
37692
+ const coordRaw = instance3.unbreakscape(v2, {
37693
+ format: TextFormat2.plainText,
37694
+ location: TextLocation2.tag
37695
+ });
37696
+ if (coordRaw == null) return void 0;
37697
+ const coordParts = coordRaw.split(",").map((p) => instance6.asNumber(p.trim()));
37698
+ if (coordParts.length !== 4 || coordParts.some((n) => n == null)) {
37699
+ return void 0;
37700
+ }
37701
+ return coordParts;
35303
37702
  }
35304
37703
  }
35305
37704
  return instance3.unbreakscape(v2, {
@@ -35336,6 +37735,8 @@ function propertyContentProcessor(context, contentDepth, tagsConfig, content, ta
35336
37735
  minCount: 1,
35337
37736
  chain: void 0,
35338
37737
  jsonKey: void 0,
37738
+ exportJsonKey: void 0,
37739
+ hasExportJsonKey: false,
35339
37740
  format: TagFormat.bitmarkText,
35340
37741
  values: void 0,
35341
37742
  defaultValue: void 0,
@@ -35346,19 +37747,18 @@ function propertyContentProcessor(context, contentDepth, tagsConfig, content, ta
35346
37747
  }
35347
37748
 
35348
37749
  // src/parser/bitmark/peg/contentProcessors/ReferenceTagContentProcessor.ts
35349
- function referenceTagContentProcessor(_context, _contentDepth, _tagsConfig, content, target, isReferenceEnd) {
35350
- const { value } = content;
37750
+ function referenceTagContentProcessor(context, _contentDepth, tagsConfig, content, target) {
37751
+ const { value, chain } = content;
35351
37752
  const trimmedStringValue = stringUtils.trimmedString(value);
35352
- if (isReferenceEnd) {
35353
- target.referenceEnd = instance3.unbreakscape(trimmedStringValue, {
35354
- format: TextFormat2.bitmarkText,
35355
- location: TextLocation2.tag
35356
- });
35357
- } else {
35358
- target.reference = instance3.unbreakscape(trimmedStringValue, {
35359
- format: TextFormat2.bitmarkText,
35360
- location: TextLocation2.tag
35361
- });
37753
+ target.reference = instance3.unbreakscape(trimmedStringValue, {
37754
+ format: TextFormat2.bitmarkText,
37755
+ location: TextLocation2.tag
37756
+ });
37757
+ if (Array.isArray(chain) && chain.length > 0) {
37758
+ const sub = context.bitContentProcessor(ContentDepth.Chain, tagsConfig, chain);
37759
+ if (sub.reference != null) {
37760
+ target.referenceEnd = sub.reference;
37761
+ }
35362
37762
  }
35363
37763
  }
35364
37764
 
@@ -36044,7 +38444,6 @@ var BitmarkPegParserProcessor = class {
36044
38444
  result.markConfig = [];
36045
38445
  result.extraProperties = {};
36046
38446
  result.internalComments = [];
36047
- let seenReference = false;
36048
38447
  let inFooter = false;
36049
38448
  const bodyParts = [];
36050
38449
  let bodyTextPart = instance3.EMPTY_STRING;
@@ -36080,15 +38479,7 @@ var BitmarkPegParserProcessor = class {
36080
38479
  defaultTagContentProcessor(this.context, contentDepth, tagsConfig, content, result);
36081
38480
  break;
36082
38481
  case TypeKey.Reference:
36083
- referenceTagContentProcessor(
36084
- this.context,
36085
- contentDepth,
36086
- tagsConfig,
36087
- content,
36088
- result,
36089
- seenReference
36090
- );
36091
- seenReference = true;
38482
+ referenceTagContentProcessor(this.context, contentDepth, tagsConfig, content, result);
36092
38483
  break;
36093
38484
  case TypeKey.Title:
36094
38485
  titleTagContentProcessor(this.context, contentDepth, tagsConfig, content, result);
@@ -40468,6 +42859,117 @@ var JsonStringGenerator = class {
40468
42859
  }
40469
42860
  };
40470
42861
 
42862
+ // src/utils/BoundingBox.ts
42863
+ function toBbox(arr) {
42864
+ if (!Array.isArray(arr) || arr.length !== 4) return null;
42865
+ if (!arr.every(Number.isFinite)) return null;
42866
+ return [arr[0], arr[1], arr[2], arr[3]];
42867
+ }
42868
+ function parseSourceBB(sourceBB) {
42869
+ if (sourceBB == null) return [];
42870
+ if (!Array.isArray(sourceBB) || sourceBB.length === 0) return [];
42871
+ const isNested = sourceBB.some(Array.isArray);
42872
+ const candidates = isNested ? sourceBB : [sourceBB];
42873
+ const out = [];
42874
+ for (const candidate of candidates) {
42875
+ const box = toBbox(candidate);
42876
+ if (box) out.push(box);
42877
+ }
42878
+ return out;
42879
+ }
42880
+ function boundingBoxIntersects(a, b) {
42881
+ return a[0] < b[2] && a[2] > b[0] && a[1] < b[3] && a[3] > b[1];
42882
+ }
42883
+ function boundingBoxUnion(boxes) {
42884
+ if (boxes.length === 0) return null;
42885
+ let [x0, y0, x1, y1] = boxes[0];
42886
+ for (let i = 1; i < boxes.length; i++) {
42887
+ const b = boxes[i];
42888
+ if (b[0] < x0) x0 = b[0];
42889
+ if (b[1] < y0) y0 = b[1];
42890
+ if (b[2] > x1) x1 = b[2];
42891
+ if (b[3] > y1) y1 = b[3];
42892
+ }
42893
+ return [x0, y0, x1, y1];
42894
+ }
42895
+ var BoundingBoxIndex = class _BoundingBoxIndex {
42896
+ constructor(entries) {
42897
+ __publicField(this, "_entries");
42898
+ __publicField(this, "bitmarkParser", new BitmarkParserGenerator());
42899
+ this._entries = entries;
42900
+ }
42901
+ /**
42902
+ * Build an index from already-parsed bits. Bits without a parseable
42903
+ * `sourceBB` are dropped. Order of the input is preserved.
42904
+ *
42905
+ * Pass `options.sourceRL` to scope the index to a single source page —
42906
+ * bits whose `sourceRL` doesn't match (or is absent) are excluded.
42907
+ */
42908
+ static fromBits(wrappers, options) {
42909
+ const filterSourceRL = options?.sourceRL;
42910
+ const entries = [];
42911
+ for (const wrapper of wrappers) {
42912
+ if (!wrapper?.bit) continue;
42913
+ if (filterSourceRL !== void 0 && wrapper.bit.sourceRL !== filterSourceRL) {
42914
+ continue;
42915
+ }
42916
+ const boxes = parseSourceBB(wrapper.bit.sourceBB);
42917
+ if (boxes.length === 0) continue;
42918
+ entries.push({ bit: wrapper.bit, wrapper, boxes });
42919
+ }
42920
+ return new _BoundingBoxIndex(entries);
42921
+ }
42922
+ /** All bits with at least one valid `sourceBB`, in input order. */
42923
+ get entries() {
42924
+ return this._entries;
42925
+ }
42926
+ /**
42927
+ * Bits whose ANY `sourceBB` box intersects `query` (strict overlap —
42928
+ * touching-only edges do NOT count as an intersection).
42929
+ */
42930
+ intersecting(query) {
42931
+ const matches = [];
42932
+ for (const entry of this._entries) {
42933
+ if (entry.boxes.some((box) => boundingBoxIntersects(query, box))) {
42934
+ matches.push(entry);
42935
+ }
42936
+ }
42937
+ return matches;
42938
+ }
42939
+ /**
42940
+ * Flat list of every individual `sourceBB` box that intersects `query`.
42941
+ * For a multi-box bit, only the boxes that themselves intersected are
42942
+ * returned.
42943
+ */
42944
+ intersectingBoxes(query) {
42945
+ const matches = [];
42946
+ for (const entry of this._entries) {
42947
+ for (const box of entry.boxes) {
42948
+ if (boundingBoxIntersects(query, box)) matches.push(box);
42949
+ }
42950
+ }
42951
+ return matches;
42952
+ }
42953
+ /**
42954
+ * Smallest axis-aligned box that fully encloses every individual `sourceBB`
42955
+ * box that intersected the query. For a multi-box bit, only the boxes that
42956
+ * themselves intersected contribute to the union — non-intersecting boxes
42957
+ * from the same bit are ignored. Returns `null` if no box matches.
42958
+ */
42959
+ enclosingBoundingBox(query) {
42960
+ return boundingBoxUnion(this.intersectingBoxes(query));
42961
+ }
42962
+ /** Render selected bits back to bitmark text. Empty input returns `''`. */
42963
+ toBitmark(entries) {
42964
+ if (entries.length === 0) return "";
42965
+ const wrappers = entries.map((e) => e.wrapper);
42966
+ return this.bitmarkParser.convert(wrappers, {
42967
+ outputFormat: Output.bitmark,
42968
+ bitmarkOptions: { prettifyJson: true }
42969
+ });
42970
+ }
42971
+ };
42972
+
40471
42973
  // src/index.ts
40472
42974
  init();
40473
42975
  // Annotate the CommonJS export names for ESM import in node:
@@ -40481,6 +42983,7 @@ init();
40481
42983
  BitmarkStringGenerator,
40482
42984
  BitmarkVersion,
40483
42985
  BodyTextFormat,
42986
+ BoundingBoxIndex,
40484
42987
  Builder,
40485
42988
  CardSetVersion,
40486
42989
  InfoFormat,