@valbuild/shared 0.125.0 → 0.127.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.
@@ -1,4 +1,4 @@
1
- import { Internal, CODE_LANGUAGES, DEFAULT_COLOR_FORMAT } from '@valbuild/core';
1
+ import { Internal, CODE_LANGUAGES, resolveSettingsModule, missingDeclaredKeys, localeOfValue, discriminatedUnionBranchOf, DEFAULT_COLOR_FORMAT, declaredKeySetOf } from '@valbuild/core';
2
2
  import { _ as _createForOfIteratorHelper, n as _objectSpread2, o as _arrayLikeToArray, q as _unsupportedIterableToArray, r as _defineProperty, s as _typeof } from '../../dist/getSitemapTree-a6cb37cb.esm.js';
3
3
  export { b as VAL_AI_SESSION_STORAGE_KEY, d as VAL_CANVAS_MESSAGE, c as VAL_CANVAS_PARAM, V as VAL_CONFIG_SESSION_STORAGE_KEY, a as VAL_THEME_SESSION_STORAGE_KEY, g as getNextAppRouterSourceFolder, l as getPageRouterSitemapTree, j as getPageRouterSourceFolder, m as getPatternFromModuleFilePath, h as getTanStackRouterSourceFolder, k as isTanStackRoutesFolder, f as isValCanvasFrame, i as isValCanvasPageMessage, e as isValCanvasStudioMessage, p as parseRoutePattern, w as withValCanvasParam } from '../../dist/getSitemapTree-a6cb37cb.esm.js';
4
4
  import { z } from 'zod';
@@ -982,19 +982,19 @@ var SerializedArraySchema = z.lazy(function () {
982
982
  opt: z["boolean"]()
983
983
  }));
984
984
  });
985
- var SerializedUnionSchema = z.lazy(function () {
986
- return z.union([z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
987
- type: z.literal("union"),
988
- key: SerializedLiteralSchema,
989
- items: z.array(SerializedLiteralSchema),
990
- opt: z["boolean"]()
991
- })), z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
992
- type: z.literal("union"),
985
+ var SerializedDiscriminatedUnionSchema = z.lazy(function () {
986
+ return z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
987
+ type: z.literal("discriminated-union"),
993
988
  key: z.string(),
994
989
  items: z.array(SerializedObjectSchema),
995
990
  opt: z["boolean"]()
996
- }))]);
991
+ }));
997
992
  });
993
+ var SerializedEnumSchema = z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
994
+ type: z.literal("enum"),
995
+ values: z.array(z.string()),
996
+ opt: z["boolean"]()
997
+ }));
998
998
  var ImageEncodeOption = z.union([z.literal(false), z.object({
999
999
  type: z.literal("webp"),
1000
1000
  quality: z.number().optional(),
@@ -1118,6 +1118,16 @@ var SerializedCodeSchema = z.object(_objectSpread2(_objectSpread2({}, commonSche
1118
1118
  options: CodeOptions.optional(),
1119
1119
  opt: z["boolean"]()
1120
1120
  }));
1121
+ var SerializedLocaleSchema = z.object({
1122
+ type: z.literal("locale"),
1123
+ render: FieldRender,
1124
+ preview: z.literal(true).optional(),
1125
+ opt: z["boolean"](),
1126
+ customValidate: z["boolean"]().optional(),
1127
+ readonly: z["boolean"]().optional(),
1128
+ hidden: z["boolean"]().optional(),
1129
+ description: z.string().optional()
1130
+ });
1121
1131
  var SerializedRouteSchema = z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
1122
1132
  type: z.literal("route"),
1123
1133
  options: z.object({
@@ -1143,10 +1153,15 @@ var SerializedSettingsSchema = z.lazy(function () {
1143
1153
  return z.object(_objectSpread2(_objectSpread2({}, commonSchemaFields), {}, {
1144
1154
  type: z.literal("settings"),
1145
1155
  items: z.record(z.string(), SerializedSchema),
1146
- opt: z["boolean"]()
1156
+ opt: z["boolean"](),
1157
+ // Which section this is, where Val has rules about it beyond its keys.
1158
+ // Dropping it would leave the Studio's validation worker running every
1159
+ // field's own rules and none of the cross-field ones — see
1160
+ // `sectionValidators` in core/src/schema/settings.ts.
1161
+ section: z.string().optional()
1147
1162
  }));
1148
1163
  });
1149
- var SerializedSchema = z.union([SerializedStringSchema, SerializedLiteralSchema, SerializedBooleanSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedArraySchema, SerializedUnionSchema, SerializedRichTextSchema, SerializedRecordSchema, SerializedKeyOfSchema, SerializedRouteSchema, SerializedFileSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedColorSchema, SerializedCodeSchema, SerializedSettingsSchema, SerializedImageSchema]);
1164
+ var SerializedSchema = z.union([SerializedStringSchema, SerializedLiteralSchema, SerializedBooleanSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedArraySchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedRichTextSchema, SerializedRecordSchema, SerializedKeyOfSchema, SerializedRouteSchema, SerializedLocaleSchema, SerializedFileSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedColorSchema, SerializedCodeSchema, SerializedSettingsSchema, SerializedImageSchema]);
1150
1165
 
1151
1166
  var ValCommit = z.object({
1152
1167
  commitSha: z.string(),
@@ -1158,6 +1173,31 @@ var ValCommit = z.object({
1158
1173
  commitMessage: z.string().nullable()
1159
1174
  });
1160
1175
 
1176
+ var ValDeployment = z.object({
1177
+ deploymentId: z.string(),
1178
+ deploymentState: z.string(),
1179
+ commitSha: z.string(),
1180
+ updatedAt: z.string(),
1181
+ createdAt: z.string(),
1182
+ /**
1183
+ * The commit message, as the deployment's host reported it.
1184
+ *
1185
+ * Val's OWN publishes carry their message on the commit (`ValCommit`), and
1186
+ * that is the one the Studio prefers — it is the message Val wrote. This is
1187
+ * for every OTHER deployment: a developer's push, a merged pull request, a
1188
+ * revert. Those have no Val commit at all, so without this the deploy feed
1189
+ * could only name them by their short sha, and "what went out at 14:02?" had
1190
+ * no answer inside the Studio.
1191
+ *
1192
+ * Optional AND nullable, and the two mean different things: absent is a
1193
+ * content service that does not report messages (or an older one), null is
1194
+ * one that reports having none. Both render as the short sha, so nothing
1195
+ * depends on telling them apart — but a schema that demanded the field would
1196
+ * reject the whole deployment feed of a service that does not send it.
1197
+ */
1198
+ commitMessage: z.string().nullable().optional()
1199
+ });
1200
+
1161
1201
  /**
1162
1202
  * The wire shapes for reading history.
1163
1203
  *
@@ -1297,7 +1337,7 @@ var ValConfig = z.object({
1297
1337
  gitCommit: z.string().optional(),
1298
1338
  gitBranch: z.string().optional()
1299
1339
  });
1300
- var ValidationFixZ = z.union([z.literal("image:add-metadata"), z.literal("image:check-metadata"), z.literal("image:check-remote"), z.literal("image:upload-remote"), z.literal("image:download-remote"), z.literal("images:check-remote"), z.literal("images:upload-remote"), z.literal("file:add-metadata"), z.literal("file:check-metadata"), z.literal("file:check-remote"), z.literal("file:upload-remote"), z.literal("file:download-remote"), z.literal("files:check-remote"), z.literal("files:upload-remote"), z.literal("keyof:check-keys"), z.literal("router:check-route"), z.literal("images:check-unique-folder"), z.literal("files:check-unique-folder"), z.literal("images:check-all-files"), z.literal("files:check-all-files"), z.literal("jsonValues:extract-entry")]);
1340
+ var ValidationFixZ = z.union([z.literal("image:add-metadata"), z.literal("image:check-metadata"), z.literal("image:check-remote"), z.literal("image:upload-remote"), z.literal("image:download-remote"), z.literal("images:check-remote"), z.literal("images:upload-remote"), z.literal("file:add-metadata"), z.literal("file:check-metadata"), z.literal("file:check-remote"), z.literal("file:upload-remote"), z.literal("file:download-remote"), z.literal("files:check-remote"), z.literal("files:upload-remote"), z.literal("keyof:check-keys"), z.literal("router:check-route"), z.literal("locale:check-locale"), z.literal("record:fill-keys"), z.literal("images:check-unique-folder"), z.literal("files:check-unique-folder"), z.literal("images:check-all-files"), z.literal("files:check-all-files"), z.literal("jsonValues:extract-entry")]);
1301
1341
  var ValidationError = z.object({
1302
1342
  message: z.string(),
1303
1343
  value: z.unknown().optional(),
@@ -1750,6 +1790,21 @@ var Api = {
1750
1790
  */
1751
1791
  headCommitSha: z.string().optional(),
1752
1792
  commits: z.array(ValCommit),
1793
+ /**
1794
+ * The publishes the content service knows about.
1795
+ *
1796
+ * Declared, at last. `getStat` has always returned these in
1797
+ * `http` mode and the Studio has always read them - the response
1798
+ * validator strips unknown keys rather than rejecting them, and
1799
+ * the raw json is what the client hands back, so they arrived
1800
+ * while the contract said they could not. That was survivable
1801
+ * until a deployment started carrying a commit MESSAGE: a field
1802
+ * nobody has declared is a field the next person deletes.
1803
+ *
1804
+ * Optional, because a content service that reports no
1805
+ * deployments sends none.
1806
+ */
1807
+ deployments: z.array(ValDeployment).optional(),
1753
1808
  config: ValConfig,
1754
1809
  profileId: z.string().nullable(),
1755
1810
  mode: z.union([z.literal("http"), z.literal("fs")])
@@ -3189,7 +3244,7 @@ var compatible = {
3189
3244
  * `readonly Json[]` — so the check that reads naturally does not type. This
3190
3245
  * predicate is that check, said once.
3191
3246
  */
3192
- function isJsonObject(value) {
3247
+ function isJsonObject$1(value) {
3193
3248
  return _typeof(value) === "object" && value !== null && !Array.isArray(value);
3194
3249
  }
3195
3250
  function incompatible(reason) {
@@ -3242,21 +3297,11 @@ function explainIncompatible(reason) {
3242
3297
  * not a failure: it is what makes the caller return `unknown` rather than guess.
3243
3298
  */
3244
3299
  function variantOf(schema, value) {
3245
- if (schema.type !== "union") {
3300
+ if (schema.type !== "discriminated-union") {
3246
3301
  return undefined;
3247
3302
  }
3248
- // String union: the value IS the discriminator.
3249
- if (typeof schema.key !== "string") {
3250
- if (typeof value !== "string") {
3251
- return undefined;
3252
- }
3253
- return schema.items.find(function (item) {
3254
- return item.type === "literal" && item.value === value;
3255
- });
3256
- }
3257
- // Object union: the discriminator is a literal at `key`.
3258
3303
  var discriminator = schema.key;
3259
- if (!isJsonObject(value)) {
3304
+ if (!isJsonObject$1(value)) {
3260
3305
  return undefined;
3261
3306
  }
3262
3307
  var tag = value[discriminator];
@@ -3272,9 +3317,6 @@ function variantOf(schema, value) {
3272
3317
  });
3273
3318
  }
3274
3319
  function describeVariant(schema, key) {
3275
- if (schema.type === "literal") {
3276
- return schema.value;
3277
- }
3278
3320
  if (schema.type === "object") {
3279
3321
  var tag = schema.items[key];
3280
3322
  if ((tag === null || tag === void 0 ? void 0 : tag.type) === "literal") {
@@ -3292,11 +3334,24 @@ function checkCompatibility(from, to) {
3292
3334
  });
3293
3335
  }
3294
3336
 
3337
+ // An enum's domain IS its schema, so the value decides: whatever the old
3338
+ // schema was — an enum, a literal, a plain string — the only question is
3339
+ // whether this string is still offered here.
3340
+ if (to.schema.type === "enum") {
3341
+ if (typeof from.value === "string" && to.schema.values.includes(from.value)) {
3342
+ return compatible;
3343
+ }
3344
+ return incompatible({
3345
+ kind: "no-matching-variant",
3346
+ variants: to.schema.values
3347
+ });
3348
+ }
3349
+
3295
3350
  // A changed union is not itself disqualifying. What matters is whether the
3296
3351
  // variant being restored still has somewhere to go — comparing the unions
3297
3352
  // wholesale would refuse a restore that is perfectly safe.
3298
- if (to.schema.type === "union") {
3299
- var key = typeof to.schema.key === "string" ? to.schema.key : "";
3353
+ if (to.schema.type === "discriminated-union") {
3354
+ var key = to.schema.key;
3300
3355
  var _iterator = _createForOfIteratorHelper(to.schema.items),
3301
3356
  _step;
3302
3357
  try {
@@ -3321,9 +3376,9 @@ function checkCompatibility(from, to) {
3321
3376
  });
3322
3377
  }
3323
3378
 
3324
- // Coming FROM a union into something that is not one: only the variant this
3325
- // value actually is matters, so narrow before comparing.
3326
- if (from.schema.type === "union") {
3379
+ // Coming FROM a discriminated union into something that is not one: only the
3380
+ // variant this value actually is matters, so narrow before comparing.
3381
+ if (from.schema.type === "discriminated-union") {
3327
3382
  var _variant = variantOf(from.schema, from.value);
3328
3383
  if (_variant === undefined) {
3329
3384
  return {
@@ -3336,6 +3391,25 @@ function checkCompatibility(from, to) {
3336
3391
  value: from.value
3337
3392
  }, to);
3338
3393
  }
3394
+
3395
+ // Coming FROM an enum into something that is not one: the value it holds is
3396
+ // all the old schema contributes, so compare as that one value.
3397
+ if (from.schema.type === "enum") {
3398
+ if (typeof from.value !== "string") {
3399
+ return {
3400
+ status: "unknown",
3401
+ message: "The old value is not one of the options it was stored as, so we cannot tell whether it fits here."
3402
+ };
3403
+ }
3404
+ return checkCompatibility({
3405
+ schema: {
3406
+ type: "literal",
3407
+ value: from.value,
3408
+ opt: from.schema.opt
3409
+ },
3410
+ value: from.value
3411
+ }, to);
3412
+ }
3339
3413
  if (from.schema.type !== to.schema.type) {
3340
3414
  return incompatible({
3341
3415
  kind: "type-changed",
@@ -3355,7 +3429,7 @@ function checkCompatibility(from, to) {
3355
3429
  }
3356
3430
  case "object":
3357
3431
  {
3358
- if (from.schema.type !== "object" || !isJsonObject(from.value)) {
3432
+ if (from.schema.type !== "object" || !isJsonObject$1(from.value)) {
3359
3433
  return {
3360
3434
  status: "unknown",
3361
3435
  message: "The old value is not shaped like an object."
@@ -3458,7 +3532,7 @@ function checkCompatibility(from, to) {
3458
3532
  }
3459
3533
  case "record":
3460
3534
  {
3461
- if (from.schema.type !== "record" || !isJsonObject(from.value)) {
3535
+ if (from.schema.type !== "record" || !isJsonObject$1(from.value)) {
3462
3536
  return {
3463
3537
  status: "unknown",
3464
3538
  message: "The old value is not a record."
@@ -3569,14 +3643,6 @@ var SharedValConfig = z.object({
3569
3643
  * Config that is shared between the client and server.
3570
3644
  */
3571
3645
 
3572
- var ValDeployment = z.object({
3573
- deploymentId: z.string(),
3574
- deploymentState: z.string(),
3575
- commitSha: z.string(),
3576
- updatedAt: z.string(),
3577
- createdAt: z.string()
3578
- });
3579
-
3580
3646
  /**
3581
3647
  * Shared route validation utilities
3582
3648
  */
@@ -3749,6 +3815,16 @@ function findSimilar(key, targets) {
3749
3815
  return a.distance - b.distance;
3750
3816
  });
3751
3817
  }
3818
+ /**
3819
+ * The record a `keyof:check-keys` error resolves against — the value and the
3820
+ * schema at the error's `sourcePath`.
3821
+ *
3822
+ * Exported for the one consumer that has to know what a resolution DEPENDED
3823
+ * on rather than what it concluded: `ValidationStore` re-reads the keys here
3824
+ * to decide whether a change to the referenced module can have moved the
3825
+ * answer. Keep this and {@link checkKeyIsValid} reading the same record, or the
3826
+ * store will invalidate on one record and the resolution answer from another.
3827
+ */
3752
3828
  function getKeyOfRecordAt(sourcePath, snapshot) {
3753
3829
  var _Internal$splitModule = Internal.splitModuleFilePathAndModulePath(sourcePath),
3754
3830
  _Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
@@ -3892,16 +3968,92 @@ function checkRouteIsValid(route, includePattern, excludePattern, snapshot) {
3892
3968
  error: false
3893
3969
  };
3894
3970
  }
3971
+
3972
+ /**
3973
+ * The languages the project declares, from its settings module.
3974
+ *
3975
+ * Empty where there is no settings module, no `locales` section, or nothing in
3976
+ * it — all of which mean the same thing: this project has not said it is
3977
+ * translated.
3978
+ */
3979
+ function declaredLocales(snapshot) {
3980
+ var _resolveSettingsModul = resolveSettingsModule(snapshot.schemas),
3981
+ moduleFilePath = _resolveSettingsModul.moduleFilePath;
3982
+ if (moduleFilePath === null) {
3983
+ return [];
3984
+ }
3985
+ var source = snapshot.sources[moduleFilePath];
3986
+ if (_typeof(source) !== "object" || source === null || Array.isArray(source)) {
3987
+ return [];
3988
+ }
3989
+ var locales = source["locales"];
3990
+ if (_typeof(locales) !== "object" || locales === null || Array.isArray(locales)) {
3991
+ return [];
3992
+ }
3993
+ var available = locales["available"];
3994
+ if (!Array.isArray(available)) {
3995
+ return [];
3996
+ }
3997
+ return available.filter(function (tag) {
3998
+ return typeof tag === "string";
3999
+ });
4000
+ }
4001
+ /**
4002
+ * The fixes {@link resolveSchemaSourceFixForError} answers for.
4003
+ *
4004
+ * A marker error carrying one of these is NOT a message: it says "ask the whole
4005
+ * project about this" and carries the value to ask with, because the schema
4006
+ * that raised it can only see its own module. Left unresolved, what a user sees
4007
+ * is the marker's own placeholder text — "should typically be processed by Val
4008
+ * internally … version mismatch" — which is alarming and says nothing.
4009
+ *
4010
+ * A list rather than a check per call site: every caller that resolves some of
4011
+ * these has to resolve all of them, and the two that leaked did so by adding a
4012
+ * fix here and not to a condition somewhere else.
4013
+ */
4014
+ var SCHEMA_SOURCE_FIXES = ["keyof:check-keys", "router:check-route", "locale:check-locale", "record:fill-keys"];
4015
+
4016
+ /** Whether an error is a marker only the whole project can answer. */
4017
+ function isSchemaSourceFixError(error) {
4018
+ var _error$fixes;
4019
+ return ((_error$fixes = error.fixes) !== null && _error$fixes !== void 0 ? _error$fixes : []).some(function (fix) {
4020
+ return SCHEMA_SOURCE_FIXES.includes(fix);
4021
+ });
4022
+ }
4023
+
3895
4024
  /**
3896
- * Resolves a single `keyof:check-keys` or `router:check-route` error against
3897
- * the given schema/source snapshot. Returns:
4025
+ * The record a `keyof:check-keys` error resolves against, or null for any other
4026
+ * error — including a malformed marker, which
4027
+ * {@link resolveSchemaSourceFixForError} reports and this has nothing to say
4028
+ * about.
4029
+ *
4030
+ * For a consumer that needs to know what the resolution READS, not what it
4031
+ * concludes: `ValidationStore` keeps this per validated module, so that a change
4032
+ * to the referenced record can invalidate the module holding the reference.
4033
+ */
4034
+ function keyOfRecordPath(error) {
4035
+ var _error$fixes2;
4036
+ if (!((_error$fixes2 = error.fixes) !== null && _error$fixes2 !== void 0 ? _error$fixes2 : []).includes("keyof:check-keys")) {
4037
+ return null;
4038
+ }
4039
+ var value = error.value;
4040
+ if (!value || _typeof(value) !== "object") {
4041
+ return null;
4042
+ }
4043
+ var sourcePath = value.sourcePath;
4044
+ return typeof sourcePath === "string" ? sourcePath : null;
4045
+ }
4046
+
4047
+ /**
4048
+ * Resolves a single marker error (see {@link SCHEMA_SOURCE_FIXES}) against the
4049
+ * given schema/source snapshot. Returns:
3898
4050
  * - null if the error is not one of these fixes (caller should pass through)
3899
- * - { status: "resolved" } if the referenced key/route is valid (drop error)
4051
+ * - { status: "resolved" } if the reference is valid (drop error)
3900
4052
  * - { status: "remaining"; error } if invalid (rewritten error to surface)
3901
4053
  */
3902
4054
  function resolveSchemaSourceFixForError(error, snapshot) {
3903
- var _error$fixes;
3904
- var fixes = (_error$fixes = error.fixes) !== null && _error$fixes !== void 0 ? _error$fixes : [];
4055
+ var _error$fixes3;
4056
+ var fixes = (_error$fixes3 = error.fixes) !== null && _error$fixes3 !== void 0 ? _error$fixes3 : [];
3905
4057
  if (fixes.includes("keyof:check-keys")) {
3906
4058
  if (!error.value) {
3907
4059
  return {
@@ -3960,6 +4112,120 @@ function resolveSchemaSourceFixForError(error, snapshot) {
3960
4112
  status: "resolved"
3961
4113
  };
3962
4114
  }
4115
+ if (fixes.includes("locale:check-locale")) {
4116
+ var value = error.value;
4117
+ if (_typeof(value) !== "object" || value === null) {
4118
+ return {
4119
+ status: "remaining",
4120
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4121
+ message: "Expected locale validation error to have a 'value' object. Found: ".concat(_typeof(value), ". ").concat(TYPE_ERROR_MESSAGE),
4122
+ typeError: true,
4123
+ fixes: undefined
4124
+ })
4125
+ };
4126
+ }
4127
+ var _ref6 = value,
4128
+ locale = _ref6.locale;
4129
+ if (typeof locale !== "string") {
4130
+ return {
4131
+ status: "remaining",
4132
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4133
+ message: "Expected locale validation error 'value' to have property 'locale' of type 'string'. Found: ".concat(_typeof(locale), ". ").concat(TYPE_ERROR_MESSAGE),
4134
+ typeError: true,
4135
+ fixes: undefined
4136
+ })
4137
+ };
4138
+ }
4139
+ var available = declaredLocales(snapshot);
4140
+ if (available.length === 0) {
4141
+ return {
4142
+ status: "remaining",
4143
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4144
+ // The project has not said it is translated, so the value is not
4145
+ // wrong so much as premature — and the fix is in another file.
4146
+ message: "This project has no languages. Declare them under 'locales.available' in the settings module before using s.locale().",
4147
+ fixes: undefined
4148
+ })
4149
+ };
4150
+ }
4151
+ if (available.includes(locale)) {
4152
+ return {
4153
+ status: "resolved"
4154
+ };
4155
+ }
4156
+ return {
4157
+ status: "remaining",
4158
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4159
+ message: "'".concat(locale, "' is not one of this project's languages: ").concat(available.map(function (each) {
4160
+ return "'".concat(each, "'");
4161
+ }).join(", ")),
4162
+ fixes: undefined
4163
+ })
4164
+ };
4165
+ }
4166
+ if (fixes.includes("record:fill-keys")) {
4167
+ var _value = error.value;
4168
+ if (_typeof(_value) !== "object" || _value === null) {
4169
+ return {
4170
+ status: "remaining",
4171
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4172
+ message: "Expected record key validation error to have a 'value' object. Found: ".concat(_typeof(_value), ". ").concat(TYPE_ERROR_MESSAGE),
4173
+ typeError: true,
4174
+ fixes: undefined
4175
+ })
4176
+ };
4177
+ }
4178
+ var _ref7 = _value,
4179
+ present = _ref7.present,
4180
+ declared = _ref7.declared;
4181
+ if (!Array.isArray(present)) {
4182
+ return {
4183
+ status: "remaining",
4184
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4185
+ message: "Expected record key validation error 'value' to have property 'present' of type 'array'. Found: ".concat(_typeof(present), ". ").concat(TYPE_ERROR_MESSAGE),
4186
+ typeError: true,
4187
+ fixes: undefined
4188
+ })
4189
+ };
4190
+ }
4191
+ var presentKeys = present.filter(function (key) {
4192
+ return typeof key === "string";
4193
+ });
4194
+ // A literal union brought its keys along; a locale record left them to be
4195
+ // read out of the settings module, which is the whole reason this check is
4196
+ // resolved here rather than in the schema.
4197
+ var declaredKeys = Array.isArray(declared) ? declared.filter(function (key) {
4198
+ return typeof key === "string";
4199
+ }) : declaredLocales(snapshot);
4200
+ if (declaredKeys.length === 0) {
4201
+ // A locale record on a project that has declared no languages. There is
4202
+ // nothing to require, and the locale field's own check already says the
4203
+ // project has not declared any — saying it twice on one record would not
4204
+ // add anything.
4205
+ return {
4206
+ status: "resolved"
4207
+ };
4208
+ }
4209
+ var missing = missingDeclaredKeys(declaredKeys, presentKeys);
4210
+ if (missing.length === 0) {
4211
+ return {
4212
+ status: "resolved"
4213
+ };
4214
+ }
4215
+ return {
4216
+ status: "remaining",
4217
+ error: _objectSpread2(_objectSpread2({}, error), {}, {
4218
+ message: "Missing ".concat(missing.length === 1 ? "key" : "keys", ": ").concat(missing.map(function (each) {
4219
+ return "'".concat(each, "'");
4220
+ }).join(", "), ". This record's keys are declared by its schema, so every one of them is an entry \u2014 an entry nobody has written yet is null, not absent."),
4221
+ value: {
4222
+ missing: missing,
4223
+ declared: declaredKeys
4224
+ },
4225
+ fixes: undefined
4226
+ })
4227
+ };
4228
+ }
3963
4229
  if (fixes.includes("router:check-route")) {
3964
4230
  if (!error.value) {
3965
4231
  return {
@@ -4021,10 +4287,10 @@ function asRegExpPattern(value) {
4021
4287
  /**
4022
4288
  * Apply schema/source fix resolution across a map of validation errors.
4023
4289
  *
4024
- * Errors with `keyof:check-keys` or `router:check-route` are resolved against
4025
- * the in-memory schema/source snapshot — valid references drop the error,
4026
- * invalid ones are rewritten with a "did you mean…" message and have their
4027
- * `fixes` cleared (so downstream code reports them as plain validation errors).
4290
+ * Errors carrying one of {@link SCHEMA_SOURCE_FIXES} are resolved against the
4291
+ * in-memory schema/source snapshot — valid references drop the error, invalid
4292
+ * ones are rewritten with a "did you mean…" message and have their `fixes`
4293
+ * cleared (so downstream code reports them as plain validation errors).
4028
4294
  * All other errors pass through untouched.
4029
4295
  *
4030
4296
  * Source paths with no remaining errors are removed from the result.
@@ -4058,6 +4324,189 @@ function resolveSchemaSourceFixes(validationErrors, snapshot) {
4058
4324
  return result;
4059
4325
  }
4060
4326
 
4327
+ /**
4328
+ * Which of the project's languages governs a path, or `null` for none.
4329
+ *
4330
+ * THE question the whole feature turns on. A locale scope is a subtree in one
4331
+ * language, and three things open one: a `locale` field on an object, a record
4332
+ * keyed by `s.locale()`, and a locale segment in a segmented key. Once this can
4333
+ * be answered from the schema and the source alone, everything downstream falls
4334
+ * out of it — the Studio's locale filter, deep links, `<html lang>`, and knowing
4335
+ * what to translate from and into.
4336
+ *
4337
+ * One implementation, so the Studio, the server and the validation worker cannot
4338
+ * disagree about what language a piece of content is in.
4339
+ *
4340
+ * `null` means no scope governs the path — which is the answer for most content
4341
+ * in most projects, and for every project that has not declared any languages.
4342
+ */
4343
+ function localeAt(path, snapshot) {
4344
+ var available = declaredLocales(snapshot);
4345
+ if (available.length === 0) {
4346
+ // Nothing can be one of no languages. Answering `null` rather than reading
4347
+ // the content is also what makes this cheap for the projects that are not
4348
+ // translated at all.
4349
+ return null;
4350
+ }
4351
+ var _Internal$splitModule = Internal.splitModuleFilePathAndModulePath(path),
4352
+ _Internal$splitModule2 = _slicedToArray(_Internal$splitModule, 2),
4353
+ moduleFilePath = _Internal$splitModule2[0],
4354
+ modulePath = _Internal$splitModule2[1];
4355
+ var schema = snapshot.schemas[moduleFilePath];
4356
+ var source = snapshot.sources[moduleFilePath];
4357
+ if (schema === undefined || source === undefined) {
4358
+ return null;
4359
+ }
4360
+ var segments = modulePath ? Internal.splitModulePath(modulePath) : [];
4361
+ return walk(schema, source, segments, available);
4362
+ }
4363
+
4364
+ /**
4365
+ * Walk from a node towards `segments`, answering with the innermost scope.
4366
+ *
4367
+ * The scope is read on ARRIVAL at each node — the node the path names included,
4368
+ * not only the ones passed through on the way to it. So `localeAt` of a
4369
+ * scope-opening object is that object's own locale rather than its parent's,
4370
+ * and a path that stops AT a block still answers with the block's language.
4371
+ *
4372
+ * A scope may not contain another (see `localeScopeErrors`), so at most one of
4373
+ * these can fire on a well-formed path; taking the innermost is what makes the
4374
+ * answer well-defined while a project is mid-fix.
4375
+ */
4376
+ function walk(schema, source, segments, available) {
4377
+ var entered = enter(schema, source, available);
4378
+ var locale = entered.locale;
4379
+ var currentSchema = entered.schema;
4380
+ var currentSource = source;
4381
+ var _iterator = _createForOfIteratorHelper(segments),
4382
+ _step;
4383
+ try {
4384
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
4385
+ var segment = _step.value;
4386
+ if (currentSchema === undefined) {
4387
+ return locale;
4388
+ }
4389
+ if (currentSchema.type === "record") {
4390
+ var _currentSchema$key;
4391
+ if (((_currentSchema$key = currentSchema.key) === null || _currentSchema$key === void 0 ? void 0 : _currentSchema$key.type) === "locale") {
4392
+ // In a locale-keyed record the KEY is the language, so the segment we
4393
+ // are about to take is the answer.
4394
+ var resolved = localeOfValue(segment, available);
4395
+ if (resolved !== null) {
4396
+ locale = resolved;
4397
+ }
4398
+ }
4399
+ currentSchema = currentSchema.item;
4400
+ } else if (currentSchema.type === "array") {
4401
+ currentSchema = currentSchema.item;
4402
+ } else if (currentSchema.type === "object" || currentSchema.type === "settings") {
4403
+ currentSchema = currentSchema.items[segment];
4404
+ } else {
4405
+ // A leaf, or richtext's internal structure. Nothing below opens a scope.
4406
+ return locale;
4407
+ }
4408
+ currentSource = childSource(currentSource, segment);
4409
+ entered = enter(currentSchema, currentSource, available);
4410
+ currentSchema = entered.schema;
4411
+ if (entered.locale !== null) {
4412
+ locale = entered.locale;
4413
+ }
4414
+ }
4415
+ } catch (err) {
4416
+ _iterator.e(err);
4417
+ } finally {
4418
+ _iterator.f();
4419
+ }
4420
+ return locale;
4421
+ }
4422
+
4423
+ /**
4424
+ * Arrive at a node: resolve what it really is, and read the scope it opens.
4425
+ *
4426
+ * A discriminated union is a fork rather than a level — the variant the value
4427
+ * takes IS the node — so it is resolved here, before anything asks what the
4428
+ * node holds. Doing that on arrival rather than on the way down is the
4429
+ * difference between a block's own path answering with its language and
4430
+ * answering with nothing.
4431
+ */
4432
+ function enter(schema, source, available) {
4433
+ var resolved = (schema === null || schema === void 0 ? void 0 : schema.type) === "discriminated-union" ? variantOfUnion(schema, source) : schema;
4434
+ return {
4435
+ schema: resolved,
4436
+ locale: resolved === undefined ? null : localeOfObjectField(resolved, source, available)
4437
+ };
4438
+ }
4439
+
4440
+ /**
4441
+ * The language an object's own `locale` field says it is in, if it has one.
4442
+ *
4443
+ * Reads the SOURCE, since unlike a record key the value is content rather than
4444
+ * part of the path. A field that has not been filled in, or holds something
4445
+ * that is not one of the project's languages, is not an answer — validation is
4446
+ * already reporting that, and guessing here would put a language in
4447
+ * `<html lang>` that nobody chose.
4448
+ */
4449
+ function localeOfObjectField(schema, source, available) {
4450
+ if (schema.type !== "object" || !isJsonObject(source)) {
4451
+ return null;
4452
+ }
4453
+ for (var _i = 0, _Object$entries = Object.entries(schema.items); _i < _Object$entries.length; _i++) {
4454
+ var _Object$entries$_i = _slicedToArray(_Object$entries[_i], 2),
4455
+ key = _Object$entries$_i[0],
4456
+ item = _Object$entries$_i[1];
4457
+ if (item.type !== "locale") {
4458
+ continue;
4459
+ }
4460
+ var value = source[key];
4461
+ if (typeof value !== "string") {
4462
+ return null;
4463
+ }
4464
+ return localeOfValue(value, available);
4465
+ }
4466
+ return null;
4467
+ }
4468
+
4469
+ /**
4470
+ * The variant a value takes, or `undefined` if the tag matches none.
4471
+ *
4472
+ * The tag is read off the source here; picking the variant is
4473
+ * {@link discriminatedUnionBranchOf}, shared with the Studio's locale filter so
4474
+ * the two cannot disagree about which variant a row is. `s.enum()` never
4475
+ * reaches this — it is a leaf, with nothing under it to walk into.
4476
+ */
4477
+ function variantOfUnion(schema, source) {
4478
+ if (!isJsonObject(source)) {
4479
+ return undefined;
4480
+ }
4481
+ return discriminatedUnionBranchOf(schema, source[schema.key]);
4482
+ }
4483
+
4484
+ /** The child of a source value at a segment, or `null` where there is none. */
4485
+ function childSource(source, segment) {
4486
+ var _source$segment;
4487
+ if (Array.isArray(source)) {
4488
+ var _source$index;
4489
+ var index = Number(segment);
4490
+ return Number.isInteger(index) ? (_source$index = source[index]) !== null && _source$index !== void 0 ? _source$index : null : null;
4491
+ }
4492
+ if (!isJsonObject(source)) {
4493
+ return null;
4494
+ }
4495
+ return (_source$segment = source[segment]) !== null && _source$segment !== void 0 ? _source$segment : null;
4496
+ }
4497
+
4498
+ /**
4499
+ * Whether `source` is a JSON object rather than an array or a primitive.
4500
+ *
4501
+ * A type predicate rather than the inline checks, because `Array.isArray` does
4502
+ * NOT narrow `JsonArray` out of `Json` — it is `readonly Json[]`, and the
4503
+ * built-in guard only narrows mutable arrays. Without this, every read of a
4504
+ * property would need an assertion.
4505
+ */
4506
+ function isJsonObject(source) {
4507
+ return _typeof(source) === "object" && source !== null && !Array.isArray(source);
4508
+ }
4509
+
4061
4510
  /**
4062
4511
  * Local `yyyy-MM-dd`, which is the one thing this module used `date-fns` for.
4063
4512
  *
@@ -4094,15 +4543,27 @@ function clampDateTimeString(value, options) {
4094
4543
  }
4095
4544
  return value;
4096
4545
  }
4097
- function emptyOf(schema) {
4546
+
4547
+ /**
4548
+ * What `emptyOf` cannot read off a serialized schema.
4549
+ *
4550
+ * The project's languages, and which one is being worked in. Neither is in the
4551
+ * schema: a locale-keyed record has one entry per language and the languages
4552
+ * are declared in the settings module, and "which language am I writing" is a
4553
+ * fact about the editor, not about the content. Optional throughout: a caller
4554
+ * that has no context gets an empty record and an unset locale, which
4555
+ * validation then reports, rather than wrong ones.
4556
+ */
4557
+
4558
+ function emptyOf(schema, context) {
4098
4559
  if (schema.type === "object") {
4099
4560
  return Object.fromEntries(Object.keys(schema.items).map(function (key) {
4100
- return [key, emptyOf(schema.items[key])];
4561
+ return [key, emptyOf(schema.items[key], context)];
4101
4562
  }));
4102
4563
  } else if (schema.type === "array") {
4103
4564
  return [];
4104
4565
  } else if (schema.type === "record") {
4105
- return {};
4566
+ return emptyRecord(schema, context);
4106
4567
  } else if (schema.type === "settings") {
4107
4568
  // Not an object of empty sections: every settings key is optional, and
4108
4569
  // absent IS the empty value. Filling the sections in would write a shape
@@ -4126,15 +4587,34 @@ function emptyOf(schema) {
4126
4587
  }
4127
4588
  } else if (schema.type === "route") {
4128
4589
  return ""; // Empty string as default route value
4590
+ } else if (schema.type === "locale") {
4591
+ var _context$selectedLoca;
4592
+ // Which languages exist is in the settings module, which `emptyOf` has no
4593
+ // access to — it works from a serialized schema alone. So a caller that
4594
+ // has narrowed to one language says so and gets it; a caller that has not
4595
+ // gets the empty string, which is not a language and which validation
4596
+ // reports. Guessing `locales[0]` instead would file content under a
4597
+ // language nobody chose, which is the thing this whole feature exists to
4598
+ // make visible.
4599
+ return (_context$selectedLoca = context === null || context === void 0 ? void 0 : context.selectedLocale) !== null && _context$selectedLoca !== void 0 ? _context$selectedLoca : "";
4129
4600
  } else if (schema.type === "file" || schema.type === "image") {
4130
4601
  return null; // returning null is the only thing we can do, however, it means that the patches cannot be applied yet since that might fail
4131
4602
  } else if (schema.type === "literal") {
4132
4603
  return schema.value;
4133
- } else if (schema.type === "union") {
4134
- if (typeof schema.key === "string") {
4135
- return emptyOf(schema.items[0]);
4604
+ } else if (schema.type === "discriminated-union") {
4605
+ // The first variant is what a new value starts as. `s.discriminatedUnion`
4606
+ // requires one, so an empty `items` means a serialized schema that was not
4607
+ // built by it — say so rather than returning `undefined`, which is not
4608
+ // JSON and would be written into a patch as a missing key.
4609
+ if (schema.items.length === 0) {
4610
+ throw Error("Cannot create an empty value for a discriminated union with no variants");
4611
+ }
4612
+ return emptyOf(schema.items[0], context);
4613
+ } else if (schema.type === "enum") {
4614
+ if (schema.values.length === 0) {
4615
+ throw Error("Cannot create an empty value for an enum with no values");
4136
4616
  }
4137
- return schema.key.value;
4617
+ return schema.values[0];
4138
4618
  } else if (schema.type === "date") {
4139
4619
  return clampDateString(formatLocalDate(new Date()), schema.options);
4140
4620
  } else if (schema.type === "dateTime") {
@@ -4155,6 +4635,32 @@ function emptyOf(schema) {
4155
4635
  throw Error("Unexpected schema type: " + JSON.stringify(_exhaustiveCheck));
4156
4636
  }
4157
4637
 
4638
+ /**
4639
+ * An empty record — with every key its schema declares already in it.
4640
+ *
4641
+ * An open record (`s.record(s.string(), item)`) starts empty, because there is
4642
+ * no key anyone could mean. A record whose key schema enumerates its keys is
4643
+ * the opposite: the keys are part of the schema, so an empty one is already
4644
+ * missing them, and handing back `{}` would create content that fails
4645
+ * validation the moment it is written.
4646
+ *
4647
+ * The entries are `null` rather than `emptyOf(item)`. A null entry reads as "not
4648
+ * filled in yet", which is what a language nobody has translated into IS —
4649
+ * whereas an entry of empty strings claims someone wrote it and left it blank,
4650
+ * and would count as translated in every list and filter downstream.
4651
+ */
4652
+ function emptyRecord(schema, context) {
4653
+ var _context$locales;
4654
+ var declared = declaredKeySetOf(schema.key);
4655
+ if (declared === null) {
4656
+ return {};
4657
+ }
4658
+ var keys = declared.kind === "literals" ? declared.keys : (_context$locales = context === null || context === void 0 ? void 0 : context.locales) !== null && _context$locales !== void 0 ? _context$locales : [];
4659
+ return Object.fromEntries(keys.map(function (key) {
4660
+ return [key, null];
4661
+ }));
4662
+ }
4663
+
4158
4664
  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"];
4159
4665
 
4160
4666
  function buildFileRefValue(filePath, metadata) {
@@ -4222,24 +4728,19 @@ function resolveSerializedSchemaAtPath(schema, path) {
4222
4728
  schema: current,
4223
4729
  remainingPath: path.slice(i)
4224
4730
  };
4225
- } else if (current.type === "union") {
4226
- if (typeof current.key === "string") {
4227
- var _iterator2 = _createForOfIteratorHelper(current.items),
4228
- _step2;
4229
- try {
4230
- for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
4231
- var variant = _step2.value;
4232
- var resolved = resolveSerializedSchemaAtPath(variant, path.slice(i));
4233
- if (resolved.kind !== "unresolved") return resolved;
4234
- }
4235
- } catch (err) {
4236
- _iterator2.e(err);
4237
- } finally {
4238
- _iterator2.f();
4731
+ } else if (current.type === "discriminated-union") {
4732
+ var _iterator2 = _createForOfIteratorHelper(current.items),
4733
+ _step2;
4734
+ try {
4735
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
4736
+ var variant = _step2.value;
4737
+ var resolved = resolveSerializedSchemaAtPath(variant, path.slice(i));
4738
+ if (resolved.kind !== "unresolved") return resolved;
4239
4739
  }
4240
- return {
4241
- kind: "unresolved"
4242
- };
4740
+ } catch (err) {
4741
+ _iterator2.e(err);
4742
+ } finally {
4743
+ _iterator2.f();
4243
4744
  }
4244
4745
  return {
4245
4746
  kind: "unresolved"
@@ -4536,7 +5037,13 @@ function buildEmptyAtPathPatch(args, moduleSchema,
4536
5037
  // Required, not optional: the occupied-key check below is the only thing
4537
5038
  // standing between "scaffold an entry" and "delete the entry that was there",
4538
5039
  // and an optional argument is one a caller forgets.
4539
- moduleSource) {
5040
+ moduleSource,
5041
+ // The project's languages, for a locale-keyed record: its entries ARE the
5042
+ // languages, and they are declared in another module, so an empty one built
5043
+ // without them is `{}` — which the completeness rule then reports as every
5044
+ // language missing. Both callers can answer: the Studio from the context, the
5045
+ // MCP tools from the snapshot they already hold. See `EmptyOfContext`.
5046
+ context) {
4540
5047
  var destinationSchema;
4541
5048
  if (args.destinationPath.length === 0) {
4542
5049
  destinationSchema = moduleSchema;
@@ -4582,7 +5089,7 @@ moduleSource) {
4582
5089
  if (occupied) {
4583
5090
  return occupied;
4584
5091
  }
4585
- var value = emptyOf(destinationSchema);
5092
+ var value = emptyOf(destinationSchema, context);
4586
5093
  return safeParsePatch([{
4587
5094
  op: decision.op,
4588
5095
  path: args.destinationPath,
@@ -4773,6 +5280,8 @@ function isSkippableFixCode(fix) {
4773
5280
  return true;
4774
5281
  case "keyof:check-keys":
4775
5282
  case "router:check-route":
5283
+ case "locale:check-locale":
5284
+ case "record:fill-keys":
4776
5285
  case "jsonValues:extract-entry":
4777
5286
  // `jsonValues:extract-entry` is an entry inlined in the `.val.ts`. The
4778
5287
  // browser cannot fix it (that means writing a new `*.val.json` and
@@ -5168,8 +5677,20 @@ function traverseSchemaSource(source, schema, path, callback) {
5168
5677
  return;
5169
5678
  }
5170
5679
 
5171
- // Handle route
5172
- if (schema.type === "route") {
5680
+ // Handle enum: a leaf, like a literal with more than one allowed value
5681
+ if (schema.type === "enum") {
5682
+ if (typeof source === "string") {
5683
+ callback({
5684
+ source: source,
5685
+ schema: schema,
5686
+ path: path
5687
+ });
5688
+ }
5689
+ return;
5690
+ }
5691
+
5692
+ // Handle route and locale: both are plain strings at a leaf
5693
+ if (schema.type === "route" || schema.type === "locale") {
5173
5694
  if (typeof source === "string") {
5174
5695
  callback({
5175
5696
  source: source,
@@ -5215,36 +5736,24 @@ function traverseSchemaSource(source, schema, path, callback) {
5215
5736
  return;
5216
5737
  }
5217
5738
 
5218
- // Handle union
5219
- if (schema.type === "union") {
5739
+ // Handle discriminated union: descend through the variant the value takes
5740
+ if (schema.type === "discriminated-union") {
5220
5741
  var schemaKey = schema.key;
5221
- if (typeof schemaKey === "string") {
5222
- // Tagged union - find matching sub-schema
5223
- if (source && _typeof(source) === "object" && !Array.isArray(source) && schemaKey in source) {
5224
- var itemKey = source[schemaKey];
5225
- if (typeof itemKey === "string") {
5226
- var schemaOfItem = schema.items.filter(function (item) {
5227
- return item.type === "object";
5228
- }).find(function (item) {
5229
- var itemKeySchema = item.items[schemaKey];
5230
- if ((itemKeySchema === null || itemKeySchema === void 0 ? void 0 : itemKeySchema.type) === "literal") {
5231
- return itemKeySchema.value === itemKey;
5232
- }
5233
- return false;
5234
- });
5235
- if (schemaOfItem) {
5236
- traverseSchemaSource(source, schemaOfItem, path, callback);
5742
+ if (source && _typeof(source) === "object" && !Array.isArray(source) && schemaKey in source) {
5743
+ var itemKey = source[schemaKey];
5744
+ if (typeof itemKey === "string") {
5745
+ var schemaOfItem = schema.items.filter(function (item) {
5746
+ return item.type === "object";
5747
+ }).find(function (item) {
5748
+ var itemKeySchema = item.items[schemaKey];
5749
+ if ((itemKeySchema === null || itemKeySchema === void 0 ? void 0 : itemKeySchema.type) === "literal") {
5750
+ return itemKeySchema.value === itemKey;
5237
5751
  }
5238
- }
5239
- }
5240
- } else {
5241
- // Literal union - treat as primitive
5242
- if (typeof source === "string") {
5243
- callback({
5244
- source: source,
5245
- schema: schema,
5246
- path: path
5752
+ return false;
5247
5753
  });
5754
+ if (schemaOfItem) {
5755
+ traverseSchemaSource(source, schemaOfItem, path, callback);
5756
+ }
5248
5757
  }
5249
5758
  }
5250
5759
  return;
@@ -5404,7 +5913,17 @@ function indexModule(searchIndex, moduleFilePath, source, schema) {
5404
5913
  var label = "";
5405
5914
 
5406
5915
  // Handle primitives
5407
- if (schema.type === "string" || schema.type === "number" || schema.type === "boolean" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route") {
5916
+ if (schema.type === "string" || schema.type === "number" || schema.type === "boolean" || schema.type === "date" || schema.type === "dateTime" || schema.type === "color" || schema.type === "code" || schema.type === "keyOf" || schema.type === "route" ||
5917
+ // An enum's value is a string the editor chose; it is as searchable as
5918
+ // any other string leaf. The old string union was never indexed at all,
5919
+ // which is the gap this closes.
5920
+ schema.type === "enum" ||
5921
+ // A locale tag, for the same reason: it is what the editor picked and
5922
+ // what they see in the field, so searching "nb-NO" has to find the
5923
+ // Norwegian rows. `traverseSchemaSource` visits these, and visiting a
5924
+ // leaf only to index it as the empty string is worse than not visiting
5925
+ // it — it looks like content that does not exist.
5926
+ schema.type === "locale") {
5408
5927
  var _source$toString, _source$toString2;
5409
5928
  searchText = (_source$toString = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString !== void 0 ? _source$toString : "";
5410
5929
  label = (_source$toString2 = source === null || source === void 0 ? void 0 : source.toString()) !== null && _source$toString2 !== void 0 ? _source$toString2 : "";
@@ -5545,4 +6064,4 @@ function performSearch(searchIndex, query) {
5545
6064
  };
5546
6065
  }
5547
6066
 
5548
- export { Api, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchGroup, PatchId, PatchJSON, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SerializedUnionSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, ValCommit, ValDeployment, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, checkCompatibility, chooseEncoded, concatModulePath, createRegExpFromPattern, createSearchIndex, createValClient, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isMimeTypeAccepted, isRemoteSchema, isSkippedSource, levenshtein, newestCommitSha, partitionValidationErrors, performSearch, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, sourcePathOfChild, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };
6067
+ export { Api, BinaryFileRef, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, ENCODE_DEFAULTS, ENCODE_MIME_TYPE_OF, FileOptions, HistoricalCommit, HistoricalModule, HistoricalPatchSet, HistoryError, ImageEncodeOption, ImageOptions, JSONValue, JSON_ENTRIES_BATCH_MAX, ModuleComparison, NEVER_ENCODED, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchGroup, PatchId, PatchJSON, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RestoreVerdict, RichTextOptions, SCHEMA_SOURCE_FIXES, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedDiscriminatedUnionSchema, SerializedEnumSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedLocaleSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedSettingsSchema, SerializedStringSchema, SharedValConfig, VAL_ENABLE_COOKIE_NAME, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, ValCommit, ValDeployment, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, buildSearchIndex, checkCompatibility, chooseEncoded, concatModulePath, createRegExpFromPattern, createSearchIndex, createValClient, declaredLocales, describeContainerAtPath, emptyOf, explainIncompatible, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, fitWithin, flattenRichText, getErrorMessageFromUnknownJson, getFilenameFromRef, getInlineImgInfo, getKeyOfRecordAt, getRefParts, getRoutesOf, getRoutesWithModulePaths, getSourceAt, indexModule, isMimeTypeAccepted, isRemoteSchema, isSchemaSourceFixError, isSkippedSource, keyOfRecordPath, levenshtein, localeAt, newestCommitSha, partitionValidationErrors, performSearch, remirrorToRichTextSource, removeModule, resolveEncodeSettings, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, sourcePathOfChild, toolNames, traverseSchemaSource, urlOf, validateRoutePatterns, withExtension };