@valbuild/shared 0.150.13 → 0.150.17

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.
@@ -1670,6 +1670,40 @@ var AnalyticsBeacon = zod.z.object({
1670
1670
  }, {
1671
1671
  message: "At most ".concat(MAX_ANALYTICS_PATHS_PER_BEACON, " paths a beacon")
1672
1672
  });
1673
+ /**
1674
+ * A Sketchpad, as the server keeps it: a rough page written in the strict
1675
+ * format of `sketchpad/sketchFormat.ts`, owned by the project and nothing
1676
+ * else. Not by an AI session -- the assistant that made it remembers its id,
1677
+ * and any chat can read it again -- and not by a page: `page` is a label for
1678
+ * which page it is about, and can be changed or cleared.
1679
+ *
1680
+ * `version` goes up by one on every change, and a change names the version
1681
+ * it was made against, so two writers cannot overwrite each other unseen.
1682
+ */
1683
+ var Sketchpad = zod.z.object({
1684
+ id: zod.z.string(),
1685
+ page: zod.z.string().nullable(),
1686
+ /** Its first heading, for a list to name it by. */
1687
+ title: zod.z.string().nullable(),
1688
+ text: zod.z.string(),
1689
+ version: zod.z.number()["int"](),
1690
+ archived: zod.z["boolean"](),
1691
+ createdAt: zod.z.string(),
1692
+ updatedAt: zod.z.string(),
1693
+ /** Profile ids; null where nobody was signed in (local dev, the demo). */
1694
+ createdBy: zod.z.string().nullable(),
1695
+ updatedBy: zod.z.string().nullable()
1696
+ });
1697
+ var SketchpadSummary = Sketchpad.omit({
1698
+ text: true
1699
+ });
1700
+ /** A line of a sketch the server would not take, and why. */
1701
+ var SketchErrorZ = zod.z.object({
1702
+ line: zod.z.number(),
1703
+ message: zod.z.string()
1704
+ });
1705
+ /** The most text a sketchpad holds, in UTF-8 bytes: a page, not a site. */
1706
+ var SKETCHPAD_MAX_TEXT_LENGTH = 256 * 1024;
1673
1707
  var Api = {
1674
1708
  "/draft/enable": {
1675
1709
  GET: {
@@ -1720,11 +1754,22 @@ var Api = {
1720
1754
  // Parsed by the handler rather than here, so a beacon of a version this
1721
1755
  // server does not know is ignored instead of refused.
1722
1756
  req: {
1723
- body: zod.z.unknown()
1757
+ body: zod.z.unknown(),
1758
+ // What a server that forwards beacons to Val's ingest has to check
1759
+ // and pass on itself: a forward loses the browser's own headers. The
1760
+ // visitor's IP is deliberately not one of them.
1761
+ headers: {
1762
+ "sec-fetch-site": zod.z.string().optional(),
1763
+ "user-agent": zod.z.string().optional(),
1764
+ "x-vercel-ip-country": zod.z.string().optional(),
1765
+ "cf-ipcountry": zod.z.string().optional(),
1766
+ "cloudfront-viewer-country": zod.z.string().optional()
1767
+ }
1724
1768
  },
1725
1769
  res: zod.z.union([
1726
- // `counted` is false wherever nothing stores the counts -- every
1727
- // server but the hosted platform's, for now.
1770
+ // `counted` is true only where the beacon was handed to Val's ingest
1771
+ // and it counted it: the hosted platform, or a self-hosted server in
1772
+ // http mode. False everywhere else, local dev included.
1728
1773
  zod.z.object({
1729
1774
  status: zod.z.literal(200),
1730
1775
  json: zod.z.object({
@@ -1733,6 +1778,12 @@ var Api = {
1733
1778
  }), zod.z.object({
1734
1779
  status: zod.z.literal(400),
1735
1780
  json: GenericError
1781
+ }),
1782
+ // Analytics is switched off for this project in Val's admin. The
1783
+ // browser script stops sending when it sees this.
1784
+ zod.z.object({
1785
+ status: zod.z.literal(410),
1786
+ json: GenericError
1736
1787
  })])
1737
1788
  }
1738
1789
  },
@@ -2401,6 +2452,124 @@ var Api = {
2401
2452
  })
2402
2453
  }
2403
2454
  },
2455
+ /**
2456
+ * The project's sketchpads: list them, or make one. See `Sketchpad`.
2457
+ * Making one answers with its id, which is what an assistant keeps to
2458
+ * read and change it later.
2459
+ */
2460
+ "/sketchpads": {
2461
+ GET: {
2462
+ req: {
2463
+ query: {
2464
+ /** `exclude` (the default), `include`, or `only`. */
2465
+ archived: onlyOneStringQueryParam.optional(),
2466
+ /** Only those about this page. */
2467
+ page: onlyOneStringQueryParam.optional()
2468
+ },
2469
+ cookies: {
2470
+ val_session: zod.z.string().optional()
2471
+ }
2472
+ },
2473
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2474
+ status: zod.z.literal(400),
2475
+ json: GenericError
2476
+ }), zod.z.object({
2477
+ status: zod.z.literal(500),
2478
+ json: GenericError
2479
+ }), zod.z.object({
2480
+ status: zod.z.literal(200),
2481
+ json: zod.z.object({
2482
+ sketchpads: zod.z.array(SketchpadSummary)
2483
+ })
2484
+ })])
2485
+ },
2486
+ POST: {
2487
+ req: {
2488
+ body: zod.z.object({
2489
+ text: zod.z.string(),
2490
+ page: zod.z.string().nullable()
2491
+ }),
2492
+ cookies: {
2493
+ val_session: zod.z.string().optional()
2494
+ }
2495
+ },
2496
+ res: zod.z.union([unauthorizedResponse, zod.z.object({
2497
+ status: zod.z.literal(400),
2498
+ json: zod.z.object({
2499
+ message: zod.z.string(),
2500
+ /** Lines of the text that are not in the sketch format. */
2501
+ errors: zod.z.array(SketchErrorZ).optional()
2502
+ })
2503
+ }), zod.z.object({
2504
+ status: zod.z.literal(500),
2505
+ json: GenericError
2506
+ }), zod.z.object({
2507
+ status: zod.z.literal(201),
2508
+ json: zod.z.object({
2509
+ sketchpad: Sketchpad
2510
+ })
2511
+ })])
2512
+ }
2513
+ },
2514
+ /** One sketchpad: read it, or change its text, its page or whether it is archived. */
2515
+ "/sketchpad": {
2516
+ GET: {
2517
+ req: {
2518
+ query: {
2519
+ id: onlyOneStringQueryParam
2520
+ },
2521
+ cookies: {
2522
+ val_session: zod.z.string().optional()
2523
+ }
2524
+ },
2525
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2526
+ status: zod.z.literal(500),
2527
+ json: GenericError
2528
+ }), zod.z.object({
2529
+ status: zod.z.literal(200),
2530
+ json: zod.z.object({
2531
+ sketchpad: Sketchpad
2532
+ })
2533
+ })])
2534
+ },
2535
+ PATCH: {
2536
+ req: {
2537
+ body: zod.z.object({
2538
+ id: zod.z.string(),
2539
+ /** The version this change was made against. */
2540
+ baseVersion: zod.z.number()["int"](),
2541
+ text: zod.z.string().optional(),
2542
+ page: zod.z.string().nullable().optional(),
2543
+ archived: zod.z["boolean"]().optional()
2544
+ }),
2545
+ cookies: {
2546
+ val_session: zod.z.string().optional()
2547
+ }
2548
+ },
2549
+ res: zod.z.union([unauthorizedResponse, notFoundResponse, zod.z.object({
2550
+ status: zod.z.literal(400),
2551
+ json: zod.z.object({
2552
+ message: zod.z.string(),
2553
+ errors: zod.z.array(SketchErrorZ).optional()
2554
+ })
2555
+ }), /** Changed since `baseVersion`: here is how it is now. */
2556
+ zod.z.object({
2557
+ status: zod.z.literal(409),
2558
+ json: zod.z.object({
2559
+ message: zod.z.string(),
2560
+ sketchpad: Sketchpad
2561
+ })
2562
+ }), zod.z.object({
2563
+ status: zod.z.literal(500),
2564
+ json: GenericError
2565
+ }), zod.z.object({
2566
+ status: zod.z.literal(200),
2567
+ json: zod.z.object({
2568
+ sketchpad: Sketchpad
2569
+ })
2570
+ })])
2571
+ }
2572
+ },
2404
2573
  "/upload/patches": {
2405
2574
  POST: {
2406
2575
  req: {
@@ -5370,6 +5539,7 @@ function resolveSchemaSourceFixes(validationErrors, snapshot) {
5370
5539
  * in most projects, and for every project that has not declared any languages.
5371
5540
  */
5372
5541
  function localeAt(path, snapshot) {
5542
+ var _localeScopeAt$locale, _localeScopeAt;
5373
5543
  var available = declaredLocales(snapshot);
5374
5544
  if (available.length === 0) {
5375
5545
  // Nothing can be one of no languages. Answering `null` rather than reading
@@ -5378,18 +5548,66 @@ function localeAt(path, snapshot) {
5378
5548
  return null;
5379
5549
  }
5380
5550
  var _Internal$splitModule = core.Internal.splitModuleFilePathAndModulePath(path),
5381
- _Internal$splitModule2 = createForOfIteratorHelper._slicedToArray(_Internal$splitModule, 2),
5382
- moduleFilePath = _Internal$splitModule2[0],
5383
- modulePath = _Internal$splitModule2[1];
5551
+ _Internal$splitModule2 = createForOfIteratorHelper._slicedToArray(_Internal$splitModule, 1),
5552
+ moduleFilePath = _Internal$splitModule2[0];
5384
5553
  var schema = snapshot.schemas[moduleFilePath];
5385
5554
  var source = snapshot.sources[moduleFilePath];
5386
5555
  if (schema === undefined || source === undefined) {
5387
5556
  return null;
5388
5557
  }
5558
+ return (_localeScopeAt$locale = (_localeScopeAt = localeScopeAt(path, schema, source, available)) === null || _localeScopeAt === void 0 ? void 0 : _localeScopeAt.locale) !== null && _localeScopeAt$locale !== void 0 ? _localeScopeAt$locale : null;
5559
+ }
5560
+
5561
+ /**
5562
+ * Where the scope governing a path opens, and how.
5563
+ *
5564
+ * - `field`: the node `depth` segments down has a locale field. `depth` 0 is the
5565
+ * module root.
5566
+ * - `record-key`: segment `depth` is a key of a locale-keyed record, which sits
5567
+ * `depth` segments down.
5568
+ * - `route-key`: segment `depth` is a route whose parameters name the language,
5569
+ * in the router `depth` segments down (in practice the module root).
5570
+ *
5571
+ * The opener is what decides where an editor goes when the Studio's language
5572
+ * changes under them — the same entry in another language for a key, the
5573
+ * parent for a field. See `relocateForLocale`.
5574
+ */
5575
+
5576
+ /**
5577
+ * The scope that governs a path, read from that module's schema and source
5578
+ * alone, or `null` where none does.
5579
+ *
5580
+ * {@link localeAt} for a caller that already has the module in hand — the
5581
+ * Studio, which has the project's languages from its own provider and would
5582
+ * otherwise have to assemble a snapshot of every module to ask about one.
5583
+ */
5584
+ function localeScopeAt(path, schema, source, available) {
5585
+ if (available.length === 0) {
5586
+ return null;
5587
+ }
5588
+ var _Internal$splitModule3 = core.Internal.splitModuleFilePathAndModulePath(path),
5589
+ _Internal$splitModule4 = createForOfIteratorHelper._slicedToArray(_Internal$splitModule3, 2),
5590
+ moduleFilePath = _Internal$splitModule4[0],
5591
+ modulePath = _Internal$splitModule4[1];
5389
5592
  var segments = modulePath ? core.Internal.splitModulePath(modulePath) : [];
5390
5593
  return walk(schema, source, segments, available, moduleFilePath);
5391
5594
  }
5392
5595
 
5596
+ /**
5597
+ * The language a whole module is in: the one its root's locale field names, or
5598
+ * `null` where its root has none.
5599
+ *
5600
+ * The only way a module is in one language as a whole — a locale-keyed record
5601
+ * or a router at the root is every one of its languages at once. Needs no path,
5602
+ * so a list of modules can ask it of each.
5603
+ */
5604
+ function localeOfModuleRoot(schema, source, available) {
5605
+ if (available.length === 0) {
5606
+ return null;
5607
+ }
5608
+ return enter(schema, source, available).locale;
5609
+ }
5610
+
5393
5611
  /**
5394
5612
  * The language a locale schema's value MEANS, or `null` if it means none of
5395
5613
  * the project's.
@@ -5435,11 +5653,32 @@ function isLocaleSchema(schema) {
5435
5653
  return schema.type === "locale" || schema.type === "enum" && schema.locales !== undefined;
5436
5654
  }
5437
5655
 
5656
+ /**
5657
+ * Whether a router's URLs name the language each page is in — one of its route
5658
+ * parameters is a locale (`s.router(router, { locale: … }, item)`).
5659
+ */
5660
+ function hasLocaleRouteParam(schema) {
5661
+ return schema.type === "record" && schema.params !== undefined && Object.values(schema.params).some(isLocaleSchema);
5662
+ }
5663
+
5438
5664
  /**
5439
5665
  * The language a router's key is in, where one of its route parameters is a
5440
5666
  * locale — `/nb/blog/hei` is `nb-NO` when `locale` maps `nb` to it.
5667
+ *
5668
+ * Answered from the key alone, with no content read: which is what lets the
5669
+ * Studio's page tree filter every page of a site without loading one of them.
5441
5670
  */
5442
5671
  function localeOfRouteKey(schema, moduleFilePath, key, available) {
5672
+ var param = localeParamOfRouteKey(schema, moduleFilePath, key);
5673
+ return param === null ? null : localeMeantBy(param.schema, param.value, available);
5674
+ }
5675
+
5676
+ /**
5677
+ * The route parameters a key gives its router, and which of them is the
5678
+ * language — or `null` where the router has no locale parameter or the key is
5679
+ * not one of its URLs.
5680
+ */
5681
+ function localeParamOfRouteKey(schema, moduleFilePath, key) {
5443
5682
  if (schema.params === undefined || schema.router === undefined) {
5444
5683
  return null;
5445
5684
  }
@@ -5456,7 +5695,12 @@ function localeOfRouteKey(schema, moduleFilePath, key, available) {
5456
5695
  name = _Object$entries$_i[0],
5457
5696
  param = _Object$entries$_i[1];
5458
5697
  if (isLocaleSchema(param) && Object.prototype.hasOwnProperty.call(values, name)) {
5459
- return localeMeantBy(param, values[name], available);
5698
+ return {
5699
+ name: name,
5700
+ schema: param,
5701
+ value: values[name],
5702
+ values: values
5703
+ };
5460
5704
  }
5461
5705
  }
5462
5706
  return null;
@@ -5476,16 +5720,22 @@ function localeOfRouteKey(schema, moduleFilePath, key, available) {
5476
5720
  */
5477
5721
  function walk(schema, source, segments, available, moduleFilePath) {
5478
5722
  var entered = enter(schema, source, available);
5479
- var locale = entered.locale;
5723
+ var scope = entered.locale === null ? null : {
5724
+ kind: "field",
5725
+ locale: entered.locale,
5726
+ depth: 0
5727
+ };
5480
5728
  var currentSchema = entered.schema;
5481
5729
  var currentSource = source;
5482
- var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(segments),
5730
+ var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(segments.entries()),
5483
5731
  _step;
5484
5732
  try {
5485
5733
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
5486
- var segment = _step.value;
5734
+ var _step$value = createForOfIteratorHelper._slicedToArray(_step.value, 2),
5735
+ index = _step$value[0],
5736
+ segment = _step$value[1];
5487
5737
  if (currentSchema === undefined) {
5488
- return locale;
5738
+ return scope;
5489
5739
  }
5490
5740
  if (currentSchema.type === "record") {
5491
5741
  if (currentSchema.key !== undefined && isLocaleSchema(currentSchema.key)) {
@@ -5494,14 +5744,24 @@ function walk(schema, source, segments, available, moduleFilePath) {
5494
5744
  // the tag it stands for when the key is an enum with `.locales()`.
5495
5745
  var resolved = localeMeantBy(currentSchema.key, segment, available);
5496
5746
  if (resolved !== null) {
5497
- locale = resolved;
5747
+ scope = {
5748
+ kind: "record-key",
5749
+ locale: resolved,
5750
+ depth: index,
5751
+ keySchema: currentSchema.key
5752
+ };
5498
5753
  }
5499
5754
  } else if (currentSchema.params !== undefined) {
5500
5755
  // A router whose URL names the language: the key is a URL, and one of
5501
5756
  // its parameters is the answer.
5502
5757
  var _resolved = localeOfRouteKey(currentSchema, moduleFilePath, segment, available);
5503
5758
  if (_resolved !== null) {
5504
- locale = _resolved;
5759
+ scope = {
5760
+ kind: "route-key",
5761
+ locale: _resolved,
5762
+ depth: index,
5763
+ router: currentSchema
5764
+ };
5505
5765
  }
5506
5766
  }
5507
5767
  currentSchema = currentSchema.item;
@@ -5511,13 +5771,17 @@ function walk(schema, source, segments, available, moduleFilePath) {
5511
5771
  currentSchema = currentSchema.items[segment];
5512
5772
  } else {
5513
5773
  // A leaf, or richtext's internal structure. Nothing below opens a scope.
5514
- return locale;
5774
+ return scope;
5515
5775
  }
5516
5776
  currentSource = childSource(currentSource, segment);
5517
5777
  entered = enter(currentSchema, currentSource, available);
5518
5778
  currentSchema = entered.schema;
5519
5779
  if (entered.locale !== null) {
5520
- locale = entered.locale;
5780
+ scope = {
5781
+ kind: "field",
5782
+ locale: entered.locale,
5783
+ depth: index + 1
5784
+ };
5521
5785
  }
5522
5786
  }
5523
5787
  } catch (err) {
@@ -5525,7 +5789,7 @@ function walk(schema, source, segments, available, moduleFilePath) {
5525
5789
  } finally {
5526
5790
  _iterator.f();
5527
5791
  }
5528
- return locale;
5792
+ return scope;
5529
5793
  }
5530
5794
 
5531
5795
  /**
@@ -5615,6 +5879,187 @@ function isJsonObject(source) {
5615
5879
  return createForOfIteratorHelper._typeof(source) === "object" && source !== null && !Array.isArray(source);
5616
5880
  }
5617
5881
 
5882
+ /**
5883
+ * Where an editor goes when the Studio's language changes under them.
5884
+ *
5885
+ * - `stay`: the path is in that language, or in none.
5886
+ * - `path`: the same entry in that language (`counterpart`), or the nearest
5887
+ * parent that is not in another language (`parent`).
5888
+ * - `home`: neither exists, so the editor goes to the Studio's start, with
5889
+ * nothing selected.
5890
+ */
5891
+
5892
+ /**
5893
+ * Where to move a path to when the Studio is switched to `locale`.
5894
+ *
5895
+ * The rule, in the order it is tried:
5896
+ *
5897
+ * 1. **The same entry in `locale`, where Val KNOWS it.** That is two cases:
5898
+ * a locale-keyed record, where the counterpart of `"en-US"` is the
5899
+ * `"nb-NO"` key of the same record; and a router whose URLs name their
5900
+ * language, where `/about` and `/nb/about` differ only in it. Only a key
5901
+ * that exists is taken, and the rest of the path is kept as far as it
5902
+ * still exists, so an editor on a field lands on that field.
5903
+ * 2. **Otherwise the nearest parent that is not in another language.** An
5904
+ * English post in a list of posts gives the list, now showing Norwegian.
5905
+ * 3. **Otherwise the Studio's start**, which is `home`: nothing selected. That
5906
+ * is where an object at the module root opened the scope (the whole module
5907
+ * is in another language), and where the parent would be a router: a list
5908
+ * of pages is not a page, and the Pages tree is how an editor sees one.
5909
+ *
5910
+ * Not the site's root page, and not any other page: which language a page is
5911
+ * in is the page's own business, and `/` can be in the language the editor
5912
+ * just left. The start screen is in no language.
5913
+ *
5914
+ * Never a guess. Two pages whose URLs differ in more than the language — a
5915
+ * Norwegian slug for a Norwegian page — are not known to be translations of
5916
+ * each other, so that is `home` rather than whichever page looks closest.
5917
+ */
5918
+ function relocateForLocale(path, locale, schema, source, available) {
5919
+ var scope = localeScopeAt(path, schema, source, available);
5920
+ if (scope === null || scope.locale === locale) {
5921
+ return {
5922
+ type: "stay"
5923
+ };
5924
+ }
5925
+ var _Internal$splitModule = core.Internal.splitModuleFilePathAndModulePath(path),
5926
+ _Internal$splitModule2 = createForOfIteratorHelper._slicedToArray(_Internal$splitModule, 2),
5927
+ moduleFilePath = _Internal$splitModule2[0],
5928
+ modulePath = _Internal$splitModule2[1];
5929
+ var segments = modulePath ? core.Internal.splitModulePath(modulePath) : [];
5930
+ var at = function at(parts) {
5931
+ return core.Internal.joinModuleFilePathAndModulePath(moduleFilePath, core.Internal.patchPathToModulePath(parts));
5932
+ };
5933
+ var prefix = segments.slice(0, scope.depth);
5934
+ var rest = segments.slice(scope.depth + 1);
5935
+ if (scope.kind === "record-key") {
5936
+ var record = sourceAt(source, prefix);
5937
+ var key = isJsonObject(record) ? Object.keys(record).find(function (candidate) {
5938
+ return localeMeantBy(scope.keySchema, candidate, available) === locale;
5939
+ }) : undefined;
5940
+ if (key !== undefined) {
5941
+ return {
5942
+ type: "path",
5943
+ path: at(existingPart(source, [].concat(createForOfIteratorHelper._toConsumableArray(prefix), [key]), rest)),
5944
+ reason: "counterpart"
5945
+ };
5946
+ }
5947
+ return {
5948
+ type: "path",
5949
+ path: at(prefix),
5950
+ reason: "parent"
5951
+ };
5952
+ }
5953
+ if (scope.kind === "route-key") {
5954
+ var router = sourceAt(source, prefix);
5955
+ var current = localeParamOfRouteKey(scope.router, moduleFilePath, segments[scope.depth]);
5956
+ if (isJsonObject(router) && current !== null) {
5957
+ var _key = Object.keys(router).find(function (candidate) {
5958
+ var other = localeParamOfRouteKey(scope.router, moduleFilePath, candidate);
5959
+ return other !== null && localeMeantBy(other.schema, other.value, available) === locale && sameRouteExcept(current.values, other.values, current.name);
5960
+ });
5961
+ if (_key !== undefined) {
5962
+ return {
5963
+ type: "path",
5964
+ path: at(existingPart(source, [].concat(createForOfIteratorHelper._toConsumableArray(prefix), [_key]), rest)),
5965
+ reason: "counterpart"
5966
+ };
5967
+ }
5968
+ }
5969
+ return {
5970
+ type: "home"
5971
+ };
5972
+ }
5973
+
5974
+ // A locale FIELD opened the scope, on the node `depth` segments down.
5975
+ if (scope.depth === 0) {
5976
+ return {
5977
+ type: "home"
5978
+ };
5979
+ }
5980
+ var parent = segments.slice(0, scope.depth - 1);
5981
+ if (parent.length === 0 && schema.type === "record" && schema.router !== undefined) {
5982
+ return {
5983
+ type: "home"
5984
+ };
5985
+ }
5986
+ return {
5987
+ type: "path",
5988
+ path: at(parent),
5989
+ reason: "parent"
5990
+ };
5991
+ }
5992
+
5993
+ /** The source at `parts` below `source`, or `null` where it runs out. */
5994
+ function sourceAt(source, parts) {
5995
+ var current = source;
5996
+ var _iterator = createForOfIteratorHelper._createForOfIteratorHelper(parts),
5997
+ _step;
5998
+ try {
5999
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
6000
+ var part = _step.value;
6001
+ current = childSource(current, part);
6002
+ }
6003
+ } catch (err) {
6004
+ _iterator.e(err);
6005
+ } finally {
6006
+ _iterator.f();
6007
+ }
6008
+ return current;
6009
+ }
6010
+
6011
+ /**
6012
+ * `base`, followed by as much of `rest` as the source still has.
6013
+ *
6014
+ * The counterpart of a field is the same field in the other language's entry,
6015
+ * but that entry can be `null` — declared and not yet written — or shorter (a
6016
+ * list with fewer items). Landing on the entry then is the right answer: it is
6017
+ * the translation the editor is about to write.
6018
+ */
6019
+ function existingPart(source, base, rest) {
6020
+ var parts = createForOfIteratorHelper._toConsumableArray(base);
6021
+ var current = sourceAt(source, base);
6022
+ var _iterator2 = createForOfIteratorHelper._createForOfIteratorHelper(rest),
6023
+ _step2;
6024
+ try {
6025
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
6026
+ var part = _step2.value;
6027
+ var next = childSource(current, part);
6028
+ if (next === null) {
6029
+ break;
6030
+ }
6031
+ parts.push(part);
6032
+ current = next;
6033
+ }
6034
+ } catch (err) {
6035
+ _iterator2.e(err);
6036
+ } finally {
6037
+ _iterator2.f();
6038
+ }
6039
+ return parts;
6040
+ }
6041
+
6042
+ /** Whether two routes' parameters agree on everything but `ignore`. */
6043
+ function sameRouteExcept(a, b, ignore) {
6044
+ var names = new Set([].concat(createForOfIteratorHelper._toConsumableArray(Object.keys(a)), createForOfIteratorHelper._toConsumableArray(Object.keys(b))));
6045
+ var _iterator3 = createForOfIteratorHelper._createForOfIteratorHelper(names),
6046
+ _step3;
6047
+ try {
6048
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
6049
+ var _a$name, _b$name;
6050
+ var name = _step3.value;
6051
+ if (name !== ignore && ((_a$name = a[name]) !== null && _a$name !== void 0 ? _a$name : null) !== ((_b$name = b[name]) !== null && _b$name !== void 0 ? _b$name : null)) {
6052
+ return false;
6053
+ }
6054
+ }
6055
+ } catch (err) {
6056
+ _iterator3.e(err);
6057
+ } finally {
6058
+ _iterator3.f();
6059
+ }
6060
+ return true;
6061
+ }
6062
+
5618
6063
  /**
5619
6064
  * Local `yyyy-MM-dd`, which is the one thing this module used `date-fns` for.
5620
6065
  *
@@ -5792,7 +6237,383 @@ function emptyRecord(schema, context) {
5792
6237
  }));
5793
6238
  }
5794
6239
 
5795
- var toolNames = ["get_all_schema", "get_source", "search_content", "validate_content", "create_patch", "add_session_image_to_gallery", "remove_image_gallery_entry", "navigate_to", "get_current_context", "get_patches", "get_source_path_from_route", "set_session_name", "show_compare_view", "ask_user_question", "duplicate_source", "empty_at_path", "count_entries", "get_record_keys"];
6240
+ var toolNames = ["get_all_schema", "get_source", "search_content", "validate_content", "create_patch", "add_session_image_to_gallery", "remove_image_gallery_entry", "navigate_to", "get_current_context", "get_patches", "get_source_path_from_route", "set_session_name", "show_compare_view", "ask_user_question", "duplicate_source", "empty_at_path", "count_entries", "get_record_keys", "list_sketchpads", "create_sketchpad", "read_sketchpad", "write_sketchpad"];
6241
+
6242
+ /**
6243
+ * A page sketch: a page written as indented text, with four primitives.
6244
+ *
6245
+ * ```
6246
+ * # Our people text (a heading: #, ## or ###)
6247
+ * We are 40 people in Oslo. text (a paragraph)
6248
+ * [image: Team photo] placeholder: [kind] or [kind: label]
6249
+ * [hero: Roof](image: /a.jpg) a placeholder with an image (or video) in it
6250
+ * icon hero M3 5h18v14H3z how every [hero] box is drawn: SVG path data
6251
+ * row its children side by side
6252
+ * col 2 its children stacked; 2 shares of the row
6253
+ * ## Design
6254
+ * col
6255
+ * [button: Join us]
6256
+ * ```
6257
+ *
6258
+ * It is a scratch pad, not a layout language: it says WHAT is on a page and
6259
+ * roughly WHERE, so an assistant and a person can agree on a page's content
6260
+ * before anyone worries about how it looks. The person never sees this text,
6261
+ * only the page it describes; the assistant never sees the page, only this.
6262
+ * So the two have to be the same thing, and the format is STRICT: there is
6263
+ * exactly one way to write what is on the screen. `checkSketch` is that
6264
+ * rule, and the server refuses a write that breaks it, with the line to fix.
6265
+ *
6266
+ * Parsing is still forgiving, so a sketch that breaks the rule shows what it
6267
+ * can rather than nothing; that is for reading, never for accepting.
6268
+ *
6269
+ * A placeholder's kind is any word, and nothing here knows what one means. The
6270
+ * assistant that writes the sketch also draws each kind's icon, as path data
6271
+ * in a 24 by 24 box, so a kind nobody thought of looks as deliberate as an
6272
+ * image. Media put in a box is written into the text too, so the assistant
6273
+ * that builds the page reads it exactly as the person left it.
6274
+ */
6275
+
6276
+ var CONTAINER = /^(row|col)(?:\s+(\d+))?$/;
6277
+ var PLACEHOLDER = /^\[([^\]:]*)(?::([^\]]*))?\](?:\((image|video):\s*([^\s)]+)\s*\))?$/;
6278
+ var HAS_MEDIA = /^\[[^\]]*\]\(/;
6279
+ var ICON = /^icon\s+(\S+)\s+([Mm][\d\s.-].*)$/;
6280
+ var PATH_DATA = /^[Mm][MmLlHhVvCcSsQqTtAaZz\d\s.,-]*$/;
6281
+ var HEADING = /^(#{1,3})\s+(.*)$/;
6282
+ function parseSketch(source) {
6283
+ var blocks = [];
6284
+ var errors = [];
6285
+ var icons = {};
6286
+ // The containers that are open at the current line, innermost last.
6287
+ var stack = [{
6288
+ type: "page",
6289
+ indent: -1,
6290
+ children: blocks,
6291
+ lastIndent: null
6292
+ }];
6293
+ source.split(/\r?\n/).forEach(function (raw, index) {
6294
+ var line = index + 1;
6295
+ var expanded = raw.replace(/\t/g, " ");
6296
+ var content = expanded.trim();
6297
+ if (content === "") {
6298
+ return;
6299
+ }
6300
+ var indent = expanded.length - expanded.trimStart().length;
6301
+
6302
+ // An icon is not a block, so it can go anywhere and closes nothing.
6303
+ var icon = ICON.exec(content);
6304
+ if (icon) {
6305
+ var path = icon[2].trim();
6306
+ if (PATH_DATA.test(path)) {
6307
+ icons[icon[1].toLowerCase()] = path;
6308
+ } else {
6309
+ errors.push({
6310
+ line: line,
6311
+ message: 'An icon is SVG path data for a 24 by 24 box, like "icon stat M3 17l6-6 4 4 8-8". Only path commands and numbers are allowed.'
6312
+ });
6313
+ }
6314
+ return;
6315
+ }
6316
+ while (stack.length > 1 && indent <= stack[stack.length - 1].indent) {
6317
+ stack.pop();
6318
+ }
6319
+ var parent = stack[stack.length - 1];
6320
+ var node = parseContent(content, line, errors);
6321
+
6322
+ // Indented further than the block above it, and that block is not a
6323
+ // container. Keep it as that block's sibling rather than dropping it.
6324
+ if (parent.lastIndent !== null && indent > parent.lastIndent) {
6325
+ errors.push({
6326
+ line: line,
6327
+ message: "Only row and col can hold other blocks. Indent this as much as the line above it."
6328
+ });
6329
+ } else {
6330
+ parent.lastIndent = indent;
6331
+ }
6332
+ if (node.type === "col" && parent.type !== "row") {
6333
+ errors.push({
6334
+ line: line,
6335
+ message: "A col only goes directly inside a row."
6336
+ });
6337
+ }
6338
+ if (node.type === "row" && parent.type === "row") {
6339
+ errors.push({
6340
+ line: line,
6341
+ message: "A row goes in a col, not directly in another row."
6342
+ });
6343
+ }
6344
+ parent.children.push(node);
6345
+ if (node.type === "row" || node.type === "col") {
6346
+ stack.push({
6347
+ type: node.type,
6348
+ indent: indent,
6349
+ children: node.children,
6350
+ lastIndent: null
6351
+ });
6352
+ }
6353
+ });
6354
+ return {
6355
+ blocks: blocks,
6356
+ icons: icons,
6357
+ errors: errors
6358
+ };
6359
+ }
6360
+ function parseContent(content, line, errors) {
6361
+ // A leading backslash keeps a line that looks like a keyword as text.
6362
+ if (content.startsWith("\\")) {
6363
+ return {
6364
+ type: "text",
6365
+ line: line,
6366
+ level: 0,
6367
+ text: content.slice(1)
6368
+ };
6369
+ }
6370
+ var container = CONTAINER.exec(content);
6371
+ if (container) {
6372
+ if (container[1] === "row") {
6373
+ if (container[2] !== undefined) {
6374
+ errors.push({
6375
+ line: line,
6376
+ message: "A row takes no number. Put it on a col: \"col ".concat(container[2], "\".")
6377
+ });
6378
+ }
6379
+ return {
6380
+ type: "row",
6381
+ line: line,
6382
+ children: []
6383
+ };
6384
+ }
6385
+ var weight = container[2] === undefined ? 1 : Number(container[2]);
6386
+ if (weight === 0) {
6387
+ errors.push({
6388
+ line: line,
6389
+ message: "A col needs a weight of 1 or more."
6390
+ });
6391
+ }
6392
+ return {
6393
+ type: "col",
6394
+ line: line,
6395
+ weight: Math.max(weight, 1),
6396
+ children: []
6397
+ };
6398
+ }
6399
+ if (content.startsWith("[")) {
6400
+ var placeholder = PLACEHOLDER.exec(content);
6401
+ if (placeholder) {
6402
+ var _placeholder$;
6403
+ var kind = placeholder[1].trim();
6404
+ var label = (_placeholder$ = placeholder[2]) === null || _placeholder$ === void 0 ? void 0 : _placeholder$.trim();
6405
+ if (/\s/.test(kind)) {
6406
+ // An icon line names a kind by one word, so a kind has to be one.
6407
+ errors.push({
6408
+ line: line,
6409
+ message: "A box's kind is one word: \"[".concat(kind.replace(/\s+/g, "-")).concat(label ? ": ".concat(label) : "", "]\".")
6410
+ });
6411
+ }
6412
+ var mediaType = placeholder[3];
6413
+ return {
6414
+ type: "placeholder",
6415
+ line: line,
6416
+ kind: kind === "" ? "block" : kind,
6417
+ label: label ? label : null,
6418
+ media: mediaType === "image" || mediaType === "video" ? {
6419
+ type: mediaType,
6420
+ src: placeholder[4]
6421
+ } : null
6422
+ };
6423
+ }
6424
+ if (HAS_MEDIA.test(content)) {
6425
+ errors.push({
6426
+ line: line,
6427
+ message: 'Media in a box is written after it as (image: url) or (video: url), like "[hero: Team photo](image: /public/val/team.jpg)".'
6428
+ });
6429
+ return {
6430
+ type: "text",
6431
+ line: line,
6432
+ level: 0,
6433
+ text: content
6434
+ };
6435
+ }
6436
+ errors.push({
6437
+ line: line,
6438
+ message: 'A placeholder is written [kind] or [kind: label], on a line of its own, like "[image: Team photo]".'
6439
+ });
6440
+ return {
6441
+ type: "text",
6442
+ line: line,
6443
+ level: 0,
6444
+ text: content
6445
+ };
6446
+ }
6447
+ var heading = HEADING.exec(content);
6448
+ if (heading) {
6449
+ var level = heading[1].length === 1 ? 1 : heading[1].length === 2 ? 2 : 3;
6450
+ return {
6451
+ type: "text",
6452
+ line: line,
6453
+ level: level,
6454
+ text: heading[2]
6455
+ };
6456
+ }
6457
+ return {
6458
+ type: "text",
6459
+ line: line,
6460
+ level: 0,
6461
+ text: content
6462
+ };
6463
+ }
6464
+
6465
+ /**
6466
+ * The line a text block is written as. Line breaks become spaces, and a
6467
+ * paragraph that would read as something else is kept as text with `\`.
6468
+ */
6469
+ function formatSketchText(level, text) {
6470
+ var flat = text.replace(/\s+/g, " ").trim();
6471
+ if (level > 0) {
6472
+ return "#".repeat(level) + " " + flat;
6473
+ }
6474
+ var ambiguous = flat.startsWith("\\") || flat.startsWith("[") || CONTAINER.test(flat) || HEADING.test(flat) || ICON.test(flat);
6475
+ return ambiguous ? "\\" + flat : flat;
6476
+ }
6477
+
6478
+ /** The line a placeholder is written as. */
6479
+ function formatSketchBox(placeholder) {
6480
+ var clean = function clean(text) {
6481
+ return text.replace(/[[\]]/g, "").replace(/\s+/g, " ").trim();
6482
+ };
6483
+ var kind = clean(placeholder.kind).replace(/:/g, "") || "block";
6484
+ var label = placeholder.label === null ? "" : clean(placeholder.label);
6485
+ var media = placeholder.media ? "(".concat(placeholder.media.type, ": ").concat(placeholder.media.src.replace(/[\s)]/g, encodeURIComponent), ")") : "";
6486
+ return "[".concat(kind).concat(label ? ": ".concat(label) : "", "]").concat(media);
6487
+ }
6488
+
6489
+ /**
6490
+ * The ONE way of writing `blocks` and `icons`: what `checkSketch` holds every
6491
+ * other text to, and what the Studio's editor writes (its own serializer is
6492
+ * tested against this one). Two spaces per level; an empty row gets an empty
6493
+ * col, so it has somewhere to type; a col outside a row is written as its
6494
+ * contents, which is all it shows; the icons last, after one blank line, and
6495
+ * no blank line anywhere else; a final newline. Empty text is not written --
6496
+ * it is not on the page.
6497
+ */
6498
+ function formatSketch(blocks, icons) {
6499
+ var lines = [];
6500
+ var _walk = function walk(node, depth, inRow) {
6501
+ var indent = " ".repeat(depth);
6502
+ switch (node.type) {
6503
+ case "text":
6504
+ if (node.text) {
6505
+ lines.push(indent + formatSketchText(node.level, node.text));
6506
+ }
6507
+ return;
6508
+ case "placeholder":
6509
+ lines.push(indent + formatSketchBox(node));
6510
+ return;
6511
+ case "row":
6512
+ lines.push(indent + "row");
6513
+ if (node.children.length === 0) {
6514
+ lines.push(indent + " col");
6515
+ }
6516
+ node.children.forEach(function (child) {
6517
+ return _walk(child, depth + 1, true);
6518
+ });
6519
+ return;
6520
+ case "col":
6521
+ if (!inRow) {
6522
+ node.children.forEach(function (child) {
6523
+ return _walk(child, depth, false);
6524
+ });
6525
+ return;
6526
+ }
6527
+ lines.push(indent + (node.weight === 1 ? "col" : "col ".concat(node.weight)));
6528
+ node.children.forEach(function (child) {
6529
+ return _walk(child, depth + 1, false);
6530
+ });
6531
+ return;
6532
+ }
6533
+ };
6534
+ blocks.forEach(function (block) {
6535
+ return _walk(block, 0, false);
6536
+ });
6537
+ var iconLines = Object.entries(icons).map(function (_ref) {
6538
+ var _ref2 = createForOfIteratorHelper._slicedToArray(_ref, 2),
6539
+ kind = _ref2[0],
6540
+ path = _ref2[1];
6541
+ return "icon ".concat(kind, " ").concat(path);
6542
+ });
6543
+ var all = [].concat(lines, createForOfIteratorHelper._toConsumableArray(iconLines.length > 0 ? [""].concat(createForOfIteratorHelper._toConsumableArray(iconLines)) : []));
6544
+ return all.length > 0 ? all.join("\n") + "\n" : "";
6545
+ }
6546
+
6547
+ /**
6548
+ * What a list names a sketch by: its first top-level heading, the biggest
6549
+ * one first. Null for a sketch with no heading, which a list then names by
6550
+ * its page.
6551
+ */
6552
+ function sketchTitle(blocks) {
6553
+ var _loop = function _loop() {
6554
+ var level = _arr[_i];
6555
+ var heading = blocks.find(function (block) {
6556
+ return block.type === "text" && block.level === level && block.text !== "";
6557
+ });
6558
+ if (heading) {
6559
+ return {
6560
+ v: heading.text
6561
+ };
6562
+ }
6563
+ },
6564
+ _ret;
6565
+ for (var _i = 0, _arr = [1, 2, 3]; _i < _arr.length; _i++) {
6566
+ _ret = _loop();
6567
+ if (_ret) return _ret.v;
6568
+ }
6569
+ return null;
6570
+ }
6571
+
6572
+ /**
6573
+ * Everything wrong with `source`, or nothing when it is a sketch exactly as
6574
+ * `formatSketch` writes it.
6575
+ *
6576
+ * Strict on purpose. The person sees the page and the assistant sees the
6577
+ * text, so a text with two spellings of one page -- a tab or two spaces, an
6578
+ * icon line here or there, a blank line or none -- is a text the assistant
6579
+ * cannot trust to be what is on the screen, and that would show it a change
6580
+ * nobody made the next time the Studio writes it back. Holding every write
6581
+ * to the one spelling rules all of that out at once.
6582
+ *
6583
+ * The parse errors come first; past those, the first line that is spelled
6584
+ * differently, with how it should be spelled.
6585
+ */
6586
+ function checkSketch(source) {
6587
+ var _parseSketch = parseSketch(source),
6588
+ blocks = _parseSketch.blocks,
6589
+ icons = _parseSketch.icons,
6590
+ errors = _parseSketch.errors;
6591
+ if (errors.length > 0) {
6592
+ return errors;
6593
+ }
6594
+ var canonical = formatSketch(blocks, icons);
6595
+ if (canonical === source) {
6596
+ return [];
6597
+ }
6598
+ if (canonical !== "" && !source.endsWith("\n")) {
6599
+ return [{
6600
+ line: source.split("\n").length,
6601
+ message: "A sketch ends with a newline."
6602
+ }];
6603
+ }
6604
+ // Compared without the final newline, so an extra one is an extra line.
6605
+ var given = source.slice(0, -1).split("\n");
6606
+ var wanted = canonical.slice(0, -1).split("\n");
6607
+ for (var i = 0; i < Math.max(given.length, wanted.length); i++) {
6608
+ if (given[i] !== wanted[i]) {
6609
+ return [{
6610
+ line: i + 1,
6611
+ message: wanted[i] === undefined ? "Nothing goes here: the sketch ends on the line before." : given[i] === "" ? "No blank lines: the one blank line in a sketch is before its icons." : given[i] === undefined ? "The sketch goes on: \"".concat(wanted[i], "\".") : "This line is written ".concat(JSON.stringify(wanted[i]), " in a sketch: two spaces per level, the icons last after one blank line, and no other blank lines.")
6612
+ }];
6613
+ }
6614
+ }
6615
+ return [];
6616
+ }
5796
6617
 
5797
6618
  function buildFileRefValue(filePath, metadata) {
5798
6619
  return createForOfIteratorHelper._objectSpread2({
@@ -8840,6 +9661,7 @@ exports.RestoreVerdict = RestoreVerdict;
8840
9661
  exports.RichTextOptions = RichTextOptions;
8841
9662
  exports.SCHEMA_SOURCE_FIXES = SCHEMA_SOURCE_FIXES;
8842
9663
  exports.SELF_CHECK_VERSION = SELF_CHECK_VERSION;
9664
+ exports.SKETCHPAD_MAX_TEXT_LENGTH = SKETCHPAD_MAX_TEXT_LENGTH;
8843
9665
  exports.STUDIO_FIXES = STUDIO_FIXES;
8844
9666
  exports.SerializedArraySchema = SerializedArraySchema;
8845
9667
  exports.SerializedBooleanSchema = SerializedBooleanSchema;
@@ -8865,6 +9687,8 @@ exports.SerializedStringSchema = SerializedStringSchema;
8865
9687
  exports.SerializedValViewSchema = SerializedValViewSchema;
8866
9688
  exports.SerializedVideoSchema = SerializedVideoSchema;
8867
9689
  exports.SharedValConfig = SharedValConfig;
9690
+ exports.Sketchpad = Sketchpad;
9691
+ exports.SketchpadSummary = SketchpadSummary;
8868
9692
  exports.VAL_ENABLE_COOKIE_NAME = VAL_ENABLE_COOKIE_NAME;
8869
9693
  exports.VAL_GREEN_RAMP = VAL_GREEN_RAMP;
8870
9694
  exports.VAL_SESSION_COOKIE = VAL_SESSION_COOKIE;
@@ -8885,6 +9709,8 @@ exports.buildRemoveImageGalleryEntryPatch = buildRemoveImageGalleryEntryPatch;
8885
9709
  exports.buildSearchIndex = buildSearchIndex;
8886
9710
  exports.chainHeadOf = chainHeadOf;
8887
9711
  exports.checkCompatibility = checkCompatibility;
9712
+ exports.checkSketch = checkSketch;
9713
+ exports.childSource = childSource;
8888
9714
  exports.chooseEncoded = chooseEncoded;
8889
9715
  exports.concatModulePath = concatModulePath;
8890
9716
  exports.contrastRatio = contrastRatio;
@@ -8903,6 +9729,9 @@ exports.findSimilar = findSimilar;
8903
9729
  exports.fitWithin = fitWithin;
8904
9730
  exports.flattenRichText = flattenRichText;
8905
9731
  exports.forEachRichTextImage = forEachRichTextImage;
9732
+ exports.formatSketch = formatSketch;
9733
+ exports.formatSketchBox = formatSketchBox;
9734
+ exports.formatSketchText = formatSketchText;
8906
9735
  exports.getErrorMessageFromUnknownJson = getErrorMessageFromUnknownJson;
8907
9736
  exports.getFilenameFromRef = getFilenameFromRef;
8908
9737
  exports.getInlineImgInfo = getInlineImgInfo;
@@ -8912,8 +9741,10 @@ exports.getRoutesOf = getRoutesOf;
8912
9741
  exports.getRoutesWithModulePaths = getRoutesWithModulePaths;
8913
9742
  exports.getSourceAt = getSourceAt;
8914
9743
  exports.getValDemo = getValDemo;
9744
+ exports.hasLocaleRouteParam = hasLocaleRouteParam;
8915
9745
  exports.indexModule = indexModule;
8916
9746
  exports.isInsidePatchSetPath = isInsidePatchSetPath;
9747
+ exports.isJsonObject = isJsonObject;
8917
9748
  exports.isLocaleSchema = isLocaleSchema;
8918
9749
  exports.isMimeTypeAccepted = isMimeTypeAccepted;
8919
9750
  exports.isRemoteSchema = isRemoteSchema;
@@ -8928,12 +9759,18 @@ exports.keyOfRecordPath = keyOfRecordPath;
8928
9759
  exports.levenshtein = levenshtein;
8929
9760
  exports.localeAt = localeAt;
8930
9761
  exports.localeMeantBy = localeMeantBy;
9762
+ exports.localeOfModuleRoot = localeOfModuleRoot;
9763
+ exports.localeOfRouteKey = localeOfRouteKey;
9764
+ exports.localeParamOfRouteKey = localeParamOfRouteKey;
9765
+ exports.localeScopeAt = localeScopeAt;
8931
9766
  exports.memoizePerRequest = memoizePerRequest;
8932
9767
  exports.newestCommitSha = newestCommitSha;
9768
+ exports.parseSketch = parseSketch;
8933
9769
  exports.partitionValidationErrors = partitionValidationErrors;
8934
9770
  exports.performSearch = performSearch;
8935
9771
  exports.proposalCustomProperties = proposalCustomProperties;
8936
9772
  exports.relativeLuminance = relativeLuminance;
9773
+ exports.relocateForLocale = relocateForLocale;
8937
9774
  exports.remirrorToRichTextSource = remirrorToRichTextSource;
8938
9775
  exports.removeModule = removeModule;
8939
9776
  exports.resolveEncodeSettings = resolveEncodeSettings;
@@ -8951,6 +9788,7 @@ exports.selfCheck = selfCheck;
8951
9788
  exports.shouldEncodeWithWasm = shouldEncodeWithWasm;
8952
9789
  exports.sideFiles = sideFiles;
8953
9790
  exports.sideSource = sideSource;
9791
+ exports.sketchTitle = sketchTitle;
8954
9792
  exports.sourcePathOfChild = sourcePathOfChild;
8955
9793
  exports.themeCustomProperties = themeCustomProperties;
8956
9794
  exports.toolNames = toolNames;