@valbuild/shared 0.113.0 → 0.116.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 } from '@valbuild/core';
1
+ import { Internal, CODE_LANGUAGES, DEFAULT_COLOR_FORMAT } from '@valbuild/core';
2
2
  import { b as base64DataUrlToUint8Array } from '../../dist/utils-00f3e1df.esm.js';
3
3
  import { z } from 'zod';
4
4
  import { fromError } from 'zod-validation-error';
@@ -2014,31 +2014,6 @@ var Api = {
2014
2014
  })])
2015
2015
  }
2016
2016
  },
2017
- "/commit-summary": {
2018
- GET: {
2019
- req: {
2020
- query: {
2021
- patch_id: z.array(PatchId)
2022
- },
2023
- cookies: {
2024
- val_session: z.string().optional()
2025
- }
2026
- },
2027
- res: z.union([unauthorizedResponse, z.object({
2028
- status: z.literal(400),
2029
- json: z.object({
2030
- message: z.string()
2031
- })
2032
- }), z.object({
2033
- status: z.literal(200),
2034
- json: z.object({
2035
- patchIds: z.array(PatchId),
2036
- baseSha: z.string(),
2037
- commitSummary: z.string().nullable()
2038
- })
2039
- })])
2040
- }
2041
- },
2042
2017
  "/save": {
2043
2018
  POST: {
2044
2019
  req: {
@@ -2210,7 +2185,21 @@ var Api = {
2210
2185
  status: z.literal(200),
2211
2186
  json: z.object({
2212
2187
  nonce: z.string(),
2213
- wsUrl: z.string()
2188
+ wsUrl: z.string(),
2189
+ /**
2190
+ * The AI providers this project can actually reach.
2191
+ *
2192
+ * AI runs on a key the org or the user brought, so an org with only
2193
+ * an Anthropic key cannot use a GPT model — asking for one gets a
2194
+ * refusal. The client owns the model catalog and picks a model whose
2195
+ * provider is in here.
2196
+ *
2197
+ * `string[]` and not an enum on purpose: the content server may add
2198
+ * a provider before the Studio knows of it, and a strict enum would
2199
+ * reject the whole response over a name this version has not heard
2200
+ * of. Unknown entries are simply ignored.
2201
+ */
2202
+ providers: z.array(z.string()).optional()
2214
2203
  })
2215
2204
  }), z.object({
2216
2205
  status: z.literal(500),
@@ -3456,4 +3445,751 @@ function withValCanvasParam(url) {
3456
3445
  return "".concat(url).concat(separator).concat(VAL_CANVAS_PARAM, "=1");
3457
3446
  }
3458
3447
 
3459
- export { Api, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, FileOptions, ImageEncodeOption, ImageOptions, JSON_ENTRIES_BATCH_MAX, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RichTextOptions, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedStringSchema, SerializedUnionSchema, SharedValConfig, VAL_AI_SESSION_STORAGE_KEY, VAL_CANVAS_MESSAGE, VAL_CANVAS_PARAM, VAL_CONFIG_SESSION_STORAGE_KEY, VAL_ENABLE_COOKIE_NAME, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_THEME_SESSION_STORAGE_KEY, ValCommit, ValDeployment, createRegExpFromPattern, createValClient, filterRoutesByPatterns, findSimilar, getErrorMessageFromUnknownJson, getNextAppRouterSitemapTree, getNextAppRouterSourceFolder, getPatternFromModuleFilePath, getRoutesOf, getRoutesWithModulePaths, isValCanvasFrame, isValCanvasPageMessage, isValCanvasStudioMessage, levenshtein, parseRoutePattern, remirrorToRichTextSource, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, richTextToRemirror, urlOf, validateRoutePatterns, withValCanvasParam };
3448
+ /**
3449
+ * Local `yyyy-MM-dd`, which is the one thing this module used `date-fns` for.
3450
+ *
3451
+ * Inlined rather than carried along: `@valbuild/shared` depends on nothing but
3452
+ * `@valbuild/core` and zod, and a whole date library is not worth adding to
3453
+ * that for a single format string. Deliberately the *local* date, not
3454
+ * `toISOString().slice(0, 10)` — the latter is UTC, so anyone west of
3455
+ * Greenwich would get yesterday's date as their "today" default.
3456
+ */
3457
+ function formatLocalDate(date) {
3458
+ var year = String(date.getFullYear()).padStart(4, "0");
3459
+ var month = String(date.getMonth() + 1).padStart(2, "0");
3460
+ var day = String(date.getDate()).padStart(2, "0");
3461
+ return "".concat(year, "-").concat(month, "-").concat(day);
3462
+ }
3463
+ function clampDateString(value, options) {
3464
+ if (options !== null && options !== void 0 && options.to && value > options.to) return options.to;
3465
+ if (options !== null && options !== void 0 && options.from && value < options.from) return options.from;
3466
+ return value;
3467
+ }
3468
+ function clampDateTimeString(value, options) {
3469
+ if (!options) return value;
3470
+ var ms = Date.parse(value);
3471
+ if (Number.isNaN(ms)) return value;
3472
+ // Stored datetime values are always UTC ISO strings, so normalize the
3473
+ // clamped bound (which may carry a timezone offset) via toISOString().
3474
+ if (options.to) {
3475
+ var toMs = Date.parse(options.to);
3476
+ if (!Number.isNaN(toMs) && ms > toMs) return new Date(toMs).toISOString();
3477
+ }
3478
+ if (options.from) {
3479
+ var fromMs = Date.parse(options.from);
3480
+ if (!Number.isNaN(fromMs) && ms < fromMs) return new Date(fromMs).toISOString();
3481
+ }
3482
+ return value;
3483
+ }
3484
+ function emptyOf(schema) {
3485
+ if (schema.type === "object") {
3486
+ return Object.fromEntries(Object.keys(schema.items).map(function (key) {
3487
+ return [key, emptyOf(schema.items[key])];
3488
+ }));
3489
+ } else if (schema.type === "array") {
3490
+ return [];
3491
+ } else if (schema.type === "record") {
3492
+ return {};
3493
+ } else if (schema.opt) {
3494
+ return null;
3495
+ } else if (schema.type === "richtext") {
3496
+ return [];
3497
+ } else if (schema.type === "string") {
3498
+ return "";
3499
+ } else if (schema.type === "boolean") {
3500
+ return false;
3501
+ } else if (schema.type === "number") {
3502
+ return 0;
3503
+ } else if (schema.type === "keyOf") {
3504
+ if (schema.values === "string") {
3505
+ return ""; // TODO: figure out this: user code might very well fail in this case
3506
+ } else {
3507
+ return schema.values[0];
3508
+ }
3509
+ } else if (schema.type === "route") {
3510
+ return ""; // Empty string as default route value
3511
+ } else if (schema.type === "file" || schema.type === "image") {
3512
+ 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
3513
+ } else if (schema.type === "literal") {
3514
+ return schema.value;
3515
+ } else if (schema.type === "union") {
3516
+ if (typeof schema.key === "string") {
3517
+ return emptyOf(schema.items[0]);
3518
+ }
3519
+ return schema.key.value;
3520
+ } else if (schema.type === "date") {
3521
+ return clampDateString(formatLocalDate(new Date()), schema.options);
3522
+ } else if (schema.type === "dateTime") {
3523
+ return clampDateTimeString(new Date().toISOString(), schema.options);
3524
+ } else if (schema.type === "color") {
3525
+ var _schema$options$forma, _schema$options;
3526
+ // Mid grey: visible against both a light and a dark canvas
3527
+ return Internal.color.formatColor({
3528
+ r: 128,
3529
+ g: 128,
3530
+ b: 128,
3531
+ a: 1
3532
+ }, (_schema$options$forma = (_schema$options = schema.options) === null || _schema$options === void 0 ? void 0 : _schema$options.format) !== null && _schema$options$forma !== void 0 ? _schema$options$forma : DEFAULT_COLOR_FORMAT);
3533
+ } else if (schema.type === "code") {
3534
+ return ""; // An empty editor: no language has a sensible starting snippet
3535
+ }
3536
+ var _exhaustiveCheck = schema;
3537
+ throw Error("Unexpected schema type: " + JSON.stringify(_exhaustiveCheck));
3538
+ }
3539
+
3540
+ 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"];
3541
+
3542
+ function buildFileRefValue(filePath, metadata) {
3543
+ return _objectSpread2({
3544
+ path: filePath
3545
+ }, metadata);
3546
+ }
3547
+ function getSourceAt(source, path) {
3548
+ var current = source;
3549
+ var _iterator = _createForOfIteratorHelper(path),
3550
+ _step;
3551
+ try {
3552
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
3553
+ var part = _step.value;
3554
+ if (current === null || current === undefined) return undefined;
3555
+ if (Array.isArray(current)) {
3556
+ var idx = Number(part);
3557
+ if (!Number.isInteger(idx)) return undefined;
3558
+ current = current[idx];
3559
+ } else if (_typeof(current) === "object") {
3560
+ current = current[part];
3561
+ } else {
3562
+ return undefined;
3563
+ }
3564
+ }
3565
+ } catch (err) {
3566
+ _iterator.e(err);
3567
+ } finally {
3568
+ _iterator.f();
3569
+ }
3570
+ return current;
3571
+ }
3572
+ /**
3573
+ * Walks `schema` along `path`, returning a structured result:
3574
+ * - `leaf`: walked the full path; resolved schema at the end.
3575
+ * - `richtext`: hit a richtext mid-walk; richtext is the resolved schema.
3576
+ * - `gallery-traversed`: hit an `s.images()` record and walked past it (i.e. the
3577
+ * path tries to address an entry inside the gallery — a clue the AI should
3578
+ * have used the gallery tool instead).
3579
+ * - `unresolved`: the path could not be walked.
3580
+ */
3581
+ function resolveSerializedSchemaAtPath(schema, path) {
3582
+ var current = schema;
3583
+ for (var i = 0; i < path.length; i++) {
3584
+ var part = path[i];
3585
+ if (current.type === "object") {
3586
+ var next = current.items[part];
3587
+ if (!next) return {
3588
+ kind: "unresolved"
3589
+ };
3590
+ current = next;
3591
+ } else if (current.type === "array") {
3592
+ current = current.item;
3593
+ } else if (current.type === "record") {
3594
+ if (current.mediaType === "images") {
3595
+ return {
3596
+ kind: "gallery-traversed",
3597
+ schema: current
3598
+ };
3599
+ }
3600
+ current = current.item;
3601
+ } else if (current.type === "richtext") {
3602
+ return {
3603
+ kind: "richtext",
3604
+ schema: current,
3605
+ remainingPath: path.slice(i)
3606
+ };
3607
+ } else if (current.type === "union") {
3608
+ if (typeof current.key === "string") {
3609
+ var _iterator2 = _createForOfIteratorHelper(current.items),
3610
+ _step2;
3611
+ try {
3612
+ for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
3613
+ var variant = _step2.value;
3614
+ var resolved = resolveSerializedSchemaAtPath(variant, path.slice(i));
3615
+ if (resolved.kind !== "unresolved") return resolved;
3616
+ }
3617
+ } catch (err) {
3618
+ _iterator2.e(err);
3619
+ } finally {
3620
+ _iterator2.f();
3621
+ }
3622
+ return {
3623
+ kind: "unresolved"
3624
+ };
3625
+ }
3626
+ return {
3627
+ kind: "unresolved"
3628
+ };
3629
+ } else {
3630
+ return {
3631
+ kind: "unresolved"
3632
+ };
3633
+ }
3634
+ }
3635
+ return {
3636
+ kind: "leaf",
3637
+ schema: current
3638
+ };
3639
+ }
3640
+ function getInlineImgInfo(richtextSchema) {
3641
+ var _richtextSchema$optio;
3642
+ if (richtextSchema.type !== "richtext") return {
3643
+ kind: "not-allowed"
3644
+ };
3645
+ var img = (_richtextSchema$optio = richtextSchema.options) === null || _richtextSchema$optio === void 0 || (_richtextSchema$optio = _richtextSchema$optio.inline) === null || _richtextSchema$optio === void 0 ? void 0 : _richtextSchema$optio.img;
3646
+ if (!img) return {
3647
+ kind: "not-allowed"
3648
+ };
3649
+ if (img === true) return {
3650
+ kind: "allowed",
3651
+ remote: false
3652
+ };
3653
+ return {
3654
+ kind: "allowed",
3655
+ remote: img.remote === true
3656
+ };
3657
+ }
3658
+ function isRemoteSchema(schema) {
3659
+ if (schema.type === "image" || schema.type === "file") {
3660
+ return schema.remote === true;
3661
+ }
3662
+ if (schema.type === "record") {
3663
+ return schema.remote === true;
3664
+ }
3665
+ return false;
3666
+ }
3667
+ function safeParsePatch(patch) {
3668
+ var parsed = Patch.safeParse(patch);
3669
+ if (!parsed.success) {
3670
+ return {
3671
+ kind: "error",
3672
+ message: "Built patch failed schema validation: ".concat(JSON.stringify(parsed.error))
3673
+ };
3674
+ }
3675
+ return {
3676
+ kind: "ok",
3677
+ patch: parsed.data
3678
+ };
3679
+ }
3680
+ function checkGallerySchema(moduleSchema) {
3681
+ if (moduleSchema.type === "record" && moduleSchema.mediaType === "images") {
3682
+ return null;
3683
+ }
3684
+ if (moduleSchema.type === "image") {
3685
+ return {
3686
+ kind: "wrong-tool",
3687
+ suggestedTool: "create_patch",
3688
+ reason: "Module is a plain image field, not an images gallery. Use create_patch with a SessionKey value."
3689
+ };
3690
+ }
3691
+ if (moduleSchema.type === "richtext") {
3692
+ return {
3693
+ kind: "wrong-tool",
3694
+ suggestedTool: "create_patch",
3695
+ reason: "Module is a richtext, not an images gallery. Use create_patch with a SessionKey inside a {tag:'img', src: SessionKey} node."
3696
+ };
3697
+ }
3698
+ return {
3699
+ kind: "error",
3700
+ message: "Module schema is not an images gallery (expected record with mediaType=\"images\", got '".concat(moduleSchema.type, "').")
3701
+ };
3702
+ }
3703
+ function buildImageGalleryPatch(args, moduleSchema) {
3704
+ var wrong = checkGallerySchema(moduleSchema);
3705
+ if (wrong) return wrong;
3706
+ var remote = isRemoteSchema(moduleSchema);
3707
+ var patch = [{
3708
+ op: "add",
3709
+ path: [args.filePath],
3710
+ value: _objectSpread2({}, args.metadata)
3711
+ }, {
3712
+ op: "file",
3713
+ path: [args.filePath],
3714
+ filePath: args.filePath,
3715
+ value: args.imageKey,
3716
+ remote: remote,
3717
+ metadata: args.metadata
3718
+ }];
3719
+ return safeParsePatch(patch);
3720
+ }
3721
+ function buildRemoveImageGalleryEntryPatch(args, moduleSchema, moduleSource) {
3722
+ var wrong = checkGallerySchema(moduleSchema);
3723
+ if (wrong) return wrong;
3724
+ if (!moduleSource || _typeof(moduleSource) !== "object" || Array.isArray(moduleSource)) {
3725
+ return {
3726
+ kind: "error",
3727
+ message: "Cannot remove from gallery: module source is not an object."
3728
+ };
3729
+ }
3730
+ var entries = moduleSource;
3731
+ if (!Object.prototype.hasOwnProperty.call(entries, args.filePath)) {
3732
+ var availableKeys = Object.keys(entries);
3733
+ var hint = availableKeys.length > 0 ? " Available entries: ".concat(availableKeys.map(function (k) {
3734
+ return "\"".concat(k, "\"");
3735
+ }).join(", "), ".") : " The gallery is empty.";
3736
+ return {
3737
+ kind: "error",
3738
+ message: "No gallery entry with file_path \"".concat(args.filePath, "\".").concat(hint)
3739
+ };
3740
+ }
3741
+ var remote = Internal.remote.splitRemoteRef(args.filePath).status === "success";
3742
+ var patch = [{
3743
+ op: "remove",
3744
+ path: [args.filePath]
3745
+ }, {
3746
+ op: "file",
3747
+ path: [args.filePath],
3748
+ filePath: args.filePath,
3749
+ value: null,
3750
+ remote: remote
3751
+ }];
3752
+ return safeParsePatch(patch);
3753
+ }
3754
+
3755
+ /**
3756
+ * Decides whether the duplicate/empty operation should produce an "add" or
3757
+ * "replace" JSON patch op by inspecting the destination's *parent* schema.
3758
+ *
3759
+ * - array / record parent → "add" (creating a new slot)
3760
+ * - object parent → "replace" (slot exists by schema definition)
3761
+ * - empty path → "replace" (overwriting the whole module)
3762
+ * - richtext / gallery parent → not supported; redirect to the right tool
3763
+ */
3764
+ var GALLERY_REDIRECT = {
3765
+ kind: "wrong-tool",
3766
+ suggestedTool: "add_session_image_to_gallery",
3767
+ reason: "Destination is a media gallery (s.images() / s.files()). Gallery entries are keyed by file path and carry a file on disk, so they cannot be created with a plain patch. Use add_session_image_to_gallery."
3768
+ };
3769
+
3770
+ /** An `s.images()` / `s.files()` record: a gallery, not an ordinary record. */
3771
+ function isGallery(schema) {
3772
+ return schema.type === "record" && schema.mediaType !== undefined;
3773
+ }
3774
+ function decideOp(moduleSchema, destinationPath) {
3775
+ // The DESTINATION itself, checked before its parent: `["gallery"]` resolves
3776
+ // its parent to the enclosing object and would otherwise look like an
3777
+ // ordinary `replace` that overwrites a whole gallery.
3778
+ var destination = resolveSerializedSchemaAtPath(moduleSchema, destinationPath);
3779
+ if (destination.kind === "gallery-traversed") {
3780
+ return GALLERY_REDIRECT;
3781
+ }
3782
+ if (destination.kind === "richtext") {
3783
+ return {
3784
+ kind: "wrong-tool",
3785
+ suggestedTool: "create_patch",
3786
+ reason: "Destination is a richtext value. duplicate_source and empty_at_path do not handle richtext edits."
3787
+ };
3788
+ }
3789
+ if (destination.kind === "leaf") {
3790
+ if (isGallery(destination.schema)) {
3791
+ return GALLERY_REDIRECT;
3792
+ }
3793
+ // A richtext value the path lands exactly ON resolves as a leaf, not as
3794
+ // `kind: "richtext"` (which only fires when the walk continues INTO it).
3795
+ if (destination.schema.type === "richtext") {
3796
+ return {
3797
+ kind: "wrong-tool",
3798
+ suggestedTool: "create_patch",
3799
+ reason: "Destination is a richtext value. duplicate_source and empty_at_path do not handle richtext edits."
3800
+ };
3801
+ }
3802
+ }
3803
+ if (destinationPath.length === 0) {
3804
+ return {
3805
+ kind: "ok",
3806
+ op: "replace",
3807
+ parent: "module"
3808
+ };
3809
+ }
3810
+ var parent = resolveSerializedSchemaAtPath(moduleSchema, destinationPath.slice(0, -1));
3811
+ if (parent.kind === "unresolved") {
3812
+ return {
3813
+ kind: "error",
3814
+ message: "Destination parent path ".concat(JSON.stringify(destinationPath.slice(0, -1)), " does not resolve in this module's schema.")
3815
+ };
3816
+ }
3817
+ if (parent.kind === "richtext") {
3818
+ return {
3819
+ kind: "wrong-tool",
3820
+ suggestedTool: "create_patch",
3821
+ reason: "Destination is inside a richtext value. duplicate_source and empty_at_path do not handle richtext edits."
3822
+ };
3823
+ }
3824
+ // `gallery-traversed` means the walk stepped INTO a gallery, so the
3825
+ // destination is a gallery entry; `isGallery` catches the case where the
3826
+ // parent IS the gallery and the destination is a new entry key in it.
3827
+ if (parent.kind === "gallery-traversed" || isGallery(parent.schema)) {
3828
+ return GALLERY_REDIRECT;
3829
+ }
3830
+ var schema = parent.schema;
3831
+ if (schema.type === "array" || schema.type === "record") {
3832
+ return {
3833
+ kind: "ok",
3834
+ op: "add",
3835
+ parent: schema.type
3836
+ };
3837
+ }
3838
+ if (schema.type === "object") {
3839
+ return {
3840
+ kind: "ok",
3841
+ op: "replace",
3842
+ parent: "object"
3843
+ };
3844
+ }
3845
+ return {
3846
+ kind: "error",
3847
+ message: "Destination parent has schema type \"".concat(schema.type, "\" \u2014 cannot use duplicate_source/empty_at_path here. Use create_patch instead.")
3848
+ };
3849
+ }
3850
+ function buildDuplicatePatch(args, moduleSchema, moduleSource) {
3851
+ var value = getSourceAt(moduleSource, args.sourcePath);
3852
+ if (value === undefined) {
3853
+ return {
3854
+ kind: "error",
3855
+ message: "Source path ".concat(JSON.stringify(args.sourcePath), " does not exist in the module. Use get_source to inspect the current contents.")
3856
+ };
3857
+ }
3858
+ var decision = decideOp(moduleSchema, args.destinationPath);
3859
+ if (decision.kind === "wrong-tool") {
3860
+ return {
3861
+ kind: "wrong-tool",
3862
+ suggestedTool: decision.suggestedTool,
3863
+ reason: decision.reason
3864
+ };
3865
+ }
3866
+ if (decision.kind === "error") {
3867
+ return {
3868
+ kind: "error",
3869
+ message: decision.message
3870
+ };
3871
+ }
3872
+ var occupied = refuseOccupiedRecordKey(decision.parent, moduleSource, args.destinationPath);
3873
+ if (occupied) {
3874
+ return occupied;
3875
+ }
3876
+ return safeParsePatch([{
3877
+ op: decision.op,
3878
+ path: args.destinationPath,
3879
+ value: value
3880
+ }]);
3881
+ }
3882
+
3883
+ /**
3884
+ * Refuse to write over an existing record entry.
3885
+ *
3886
+ * `add` into a record replaces whatever is at that key, so duplicating or
3887
+ * scaffolding onto an occupied key silently destroys the entry that was there.
3888
+ * A caller guessing at keys — an agent especially — hits this by accident, and
3889
+ * nothing downstream reports it: the patch is valid, the content stays valid,
3890
+ * and an entry is simply gone.
3891
+ *
3892
+ * Only records. An array `add` inserts before the index rather than replacing
3893
+ * it, and an object's fields exist by definition, so `replace` on one is the
3894
+ * whole point rather than a mistake.
3895
+ */
3896
+ function refuseOccupiedRecordKey(parent, moduleSource, destinationPath) {
3897
+ if (parent !== "record") {
3898
+ return null;
3899
+ }
3900
+ if (getSourceAt(moduleSource, destinationPath) === undefined) {
3901
+ return null;
3902
+ }
3903
+ var key = destinationPath[destinationPath.length - 1];
3904
+ return {
3905
+ kind: "error",
3906
+ message: "Destination key ".concat(JSON.stringify(key), " already exists, and writing to it would replace the entry that is there. Pick a key that is free -- get_record_keys lists the ones in use -- or use create_patch if you meant to change the existing entry.")
3907
+ };
3908
+ }
3909
+ function buildEmptyAtPathPatch(args, moduleSchema,
3910
+ // Required, not optional: the occupied-key check below is the only thing
3911
+ // standing between "scaffold an entry" and "delete the entry that was there",
3912
+ // and an optional argument is one a caller forgets.
3913
+ moduleSource) {
3914
+ var destinationSchema;
3915
+ if (args.destinationPath.length === 0) {
3916
+ destinationSchema = moduleSchema;
3917
+ } else {
3918
+ var result = resolveSerializedSchemaAtPath(moduleSchema, args.destinationPath);
3919
+ if (result.kind === "unresolved") {
3920
+ return {
3921
+ kind: "error",
3922
+ message: "Destination path ".concat(JSON.stringify(args.destinationPath), " does not resolve in this module's schema.")
3923
+ };
3924
+ }
3925
+ if (result.kind === "richtext") {
3926
+ return {
3927
+ kind: "wrong-tool",
3928
+ suggestedTool: "create_patch",
3929
+ reason: "Destination is a richtext value. Use create_patch to build richtext content."
3930
+ };
3931
+ }
3932
+ if (result.kind === "gallery-traversed") {
3933
+ return {
3934
+ kind: "wrong-tool",
3935
+ suggestedTool: "add_session_image_to_gallery",
3936
+ reason: "Destination is inside an images gallery. Use add_session_image_to_gallery to add entries."
3937
+ };
3938
+ }
3939
+ destinationSchema = result.schema;
3940
+ }
3941
+ var decision = decideOp(moduleSchema, args.destinationPath);
3942
+ if (decision.kind === "wrong-tool") {
3943
+ return {
3944
+ kind: "wrong-tool",
3945
+ suggestedTool: decision.suggestedTool,
3946
+ reason: decision.reason
3947
+ };
3948
+ }
3949
+ if (decision.kind === "error") {
3950
+ return {
3951
+ kind: "error",
3952
+ message: decision.message
3953
+ };
3954
+ }
3955
+ var occupied = refuseOccupiedRecordKey(decision.parent, moduleSource, args.destinationPath);
3956
+ if (occupied) {
3957
+ return occupied;
3958
+ }
3959
+ var value = emptyOf(destinationSchema);
3960
+ return safeParsePatch([{
3961
+ op: decision.op,
3962
+ path: args.destinationPath,
3963
+ value: value
3964
+ }]);
3965
+ }
3966
+
3967
+ /**
3968
+ * What kind of container a path points at.
3969
+ *
3970
+ * `gallery` and `richtext` are called out separately from `record`/`array`
3971
+ * because the two callers disagree about them: `count_entries` can count either,
3972
+ * `get_record_keys` documents that it refuses both.
3973
+ */
3974
+
3975
+ /**
3976
+ * Classifies the value at `path` using the SCHEMA, not just its runtime shape.
3977
+ *
3978
+ * The runtime shape is not enough: an `s.image()`/`s.file()` source, a richtext
3979
+ * node and a union variant are all plain objects at runtime, so classifying by
3980
+ * `typeof` alone let `get_record_keys` list a file ref's `_ref`/`_type` internals
3981
+ * or a richtext node's `tag`/`children` as if they were record keys.
3982
+ */
3983
+ function describeContainerAtPath(moduleSchema, moduleSource, path) {
3984
+ var value = getSourceAt(moduleSource, path);
3985
+ if (value === undefined) {
3986
+ return {
3987
+ kind: "error",
3988
+ reason: "missing",
3989
+ message: "Path ".concat(JSON.stringify(path), " does not exist in the module source. Use get_source to inspect the current contents.")
3990
+ };
3991
+ }
3992
+ var resolved = resolveSerializedSchemaAtPath(moduleSchema, path);
3993
+ if (resolved.kind === "unresolved") {
3994
+ return {
3995
+ kind: "error",
3996
+ reason: "missing",
3997
+ message: "Path ".concat(JSON.stringify(path), " does not resolve in this module's schema.")
3998
+ };
3999
+ }
4000
+ if (resolved.kind === "richtext") {
4001
+ // Mid-richtext: the path addresses a node inside the richtext value, whose
4002
+ // internals (tag, children, ...) are not entries.
4003
+ return {
4004
+ kind: "error",
4005
+ reason: "not-a-container",
4006
+ message: "Path ".concat(JSON.stringify(path), " points inside a richtext value, which has no entries to count or list.")
4007
+ };
4008
+ }
4009
+ if (resolved.kind === "gallery-traversed") {
4010
+ return {
4011
+ kind: "error",
4012
+ reason: "not-a-container",
4013
+ message: "Path ".concat(JSON.stringify(path), " points inside a gallery entry, which has no entries to count or list.")
4014
+ };
4015
+ }
4016
+ var schema = resolved.schema;
4017
+ if (schema.type === "richtext") {
4018
+ if (!Array.isArray(value)) {
4019
+ return {
4020
+ kind: "error",
4021
+ reason: "not-a-container",
4022
+ message: "Path ".concat(JSON.stringify(path), " is a richtext value but its source is not a block array.")
4023
+ };
4024
+ }
4025
+ return {
4026
+ kind: "ok",
4027
+ container: "richtext",
4028
+ value: value
4029
+ };
4030
+ }
4031
+ if (schema.type === "array") {
4032
+ if (!Array.isArray(value)) {
4033
+ return {
4034
+ kind: "error",
4035
+ reason: "not-a-container",
4036
+ message: "Path ".concat(JSON.stringify(path), " is an array in the schema but its source is not an array.")
4037
+ };
4038
+ }
4039
+ return {
4040
+ kind: "ok",
4041
+ container: "array",
4042
+ value: value
4043
+ };
4044
+ }
4045
+ if (schema.type === "record" || schema.type === "object") {
4046
+ if (value === null || _typeof(value) !== "object" || Array.isArray(value)) {
4047
+ return {
4048
+ kind: "error",
4049
+ reason: "not-a-container",
4050
+ message: "Path ".concat(JSON.stringify(path), " points to a ").concat(value === null ? "null" : Array.isArray(value) ? "array" : _typeof(value), " value, which has no entries to count or list.")
4051
+ };
4052
+ }
4053
+ return {
4054
+ kind: "ok",
4055
+ container: schema.type === "record" ? isGallery(schema) ? "gallery" : "record" : "object",
4056
+ value: value
4057
+ };
4058
+ }
4059
+ // image / file / union / string / number / boolean / date / literal / keyOf /
4060
+ // route: not containers, whatever their runtime shape looks like.
4061
+ return {
4062
+ kind: "error",
4063
+ reason: "not-a-container",
4064
+ message: "Path ".concat(JSON.stringify(path), " points to a \"").concat(schema.type, "\" value, which has no entries to count or list.")
4065
+ };
4066
+ }
4067
+
4068
+ /**
4069
+ * Partition validation errors by whether the UI should surface them.
4070
+ *
4071
+ * `skipped` errors carry only fix codes the user can't (or shouldn't) act on
4072
+ * in the browser:
4073
+ * - image/file metadata checks (need to read bytes from disk)
4074
+ * - remote upload/download/check (need a personal access token + network)
4075
+ * - gallery directory uniqueness + untracked-files checks (need fs traversal)
4076
+ *
4077
+ * The server (ValOps + createFixPatch) resolves all of these automatically on
4078
+ * save, and the CLI's `runValidation` does the same. So the user never needs
4079
+ * to see them in the studio — but we keep them in the `skipped` partition,
4080
+ * not dropped, so we can later surface a "N fixes pending — saving will
4081
+ * repair them" badge or wire up an explicit "fix all" affordance without
4082
+ * having to plumb the data back in.
4083
+ *
4084
+ * `keyof:check-keys` and `router:check-route` are surfaced if they reach
4085
+ * this function — they're meant to be resolved upfront by the shared
4086
+ * `resolveSchemaSourceFixes`. A remnant here means resolution didn't run
4087
+ * for some reason; the user-facing "Invalid key/route" message is more
4088
+ * useful than silent hiding.
4089
+ */
4090
+ function partitionValidationErrors(validationErrors) {
4091
+ var surfaced = {};
4092
+ var skipped = {};
4093
+ for (var sourcePathS in validationErrors) {
4094
+ var sourcePath = sourcePathS;
4095
+ var _iterator = _createForOfIteratorHelper(validationErrors[sourcePath]),
4096
+ _step;
4097
+ try {
4098
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
4099
+ var error = _step.value;
4100
+ if (isSkippedFix(error)) {
4101
+ var _skipped$sourcePath;
4102
+ ((_skipped$sourcePath = skipped[sourcePath]) !== null && _skipped$sourcePath !== void 0 ? _skipped$sourcePath : skipped[sourcePath] = []).push(error);
4103
+ } else {
4104
+ var _surfaced$sourcePath;
4105
+ ((_surfaced$sourcePath = surfaced[sourcePath]) !== null && _surfaced$sourcePath !== void 0 ? _surfaced$sourcePath : surfaced[sourcePath] = []).push(error);
4106
+ }
4107
+ }
4108
+ } catch (err) {
4109
+ _iterator.e(err);
4110
+ } finally {
4111
+ _iterator.f();
4112
+ }
4113
+ }
4114
+ return {
4115
+ surfaced: surfaced,
4116
+ skipped: skipped
4117
+ };
4118
+ }
4119
+ function isSkippedFix(error) {
4120
+ var _error$fixes;
4121
+ var fixes = (_error$fixes = error.fixes) !== null && _error$fixes !== void 0 ? _error$fixes : [];
4122
+ if (fixes.length === 0) return false;
4123
+ // Skip iff EVERY fix on the error is in the skip set — an error tagged
4124
+ // with both a surfaceable and a skippable fix must still be shown.
4125
+ return fixes.every(isSkippableFixCode);
4126
+ }
4127
+ function isSkippableFixCode(fix) {
4128
+ switch (fix) {
4129
+ case "image:add-metadata":
4130
+ case "image:check-metadata":
4131
+ case "image:upload-remote":
4132
+ case "image:download-remote":
4133
+ case "image:check-remote":
4134
+ case "images:check-remote":
4135
+ case "images:upload-remote":
4136
+ case "file:add-metadata":
4137
+ case "file:check-metadata":
4138
+ case "file:upload-remote":
4139
+ case "file:download-remote":
4140
+ case "file:check-remote":
4141
+ case "files:check-remote":
4142
+ case "files:upload-remote":
4143
+ case "images:check-unique-folder":
4144
+ case "files:check-unique-folder":
4145
+ case "images:check-all-files":
4146
+ case "files:check-all-files":
4147
+ return true;
4148
+ case "keyof:check-keys":
4149
+ case "router:check-route":
4150
+ case "jsonValues:extract-entry":
4151
+ // `jsonValues:extract-entry` is an entry inlined in the `.val.ts`. The
4152
+ // browser cannot fix it (that means writing a new `*.val.json` and
4153
+ // rewriting the module) and saving does not repair it either, so surface
4154
+ // it: the message names the command (`val validate --fix`) that does.
4155
+ return false;
4156
+ default:
4157
+ {
4158
+ return false;
4159
+ }
4160
+ }
4161
+ }
4162
+
4163
+ /**
4164
+ * The errors a user must actually act on.
4165
+ *
4166
+ * Two passes stand between a schema's complaint and something worth blocking a
4167
+ * publish for:
4168
+ *
4169
+ * - `resolveSchemaSourceFixes` resolves the CROSS-MODULE fixes —
4170
+ * `keyof:check-keys` and `router:check-route` — against the live schema and
4171
+ * source. Unresolved, those arrive as "This error should typically be
4172
+ * processed by Val internally. Seeing this error most likely means you have a
4173
+ * Val version mismatch", which is not something a user can do anything with.
4174
+ * - `partitionValidationErrors` drops the fixes the SERVER applies on save:
4175
+ * image and file metadata, remote files, gallery directory checks. Blocking on
4176
+ * those would refuse a publish for something the publish itself repairs.
4177
+ *
4178
+ * ## Everything that gates on validation must use this
4179
+ *
4180
+ * That is the whole reason it lives in `validation/` rather than beside one of
4181
+ * its callers. There are three: the errors a field shows, the AI flow's check on
4182
+ * a proposed patch, and — the one that bites — `system.publish`. A publish gate
4183
+ * reading raw errors refuses on `router:check-route` for every route module in
4184
+ * the project, which is a Save button that does nothing and says
4185
+ * "validation-errors" with no error visible anywhere on screen.
4186
+ */
4187
+ function filterBlockingValidationErrors(validationErrors, schemas, sources) {
4188
+ var resolved = resolveSchemaSourceFixes(validationErrors, {
4189
+ schemas: schemas !== null && schemas !== void 0 ? schemas : {},
4190
+ sources: sources !== null && sources !== void 0 ? sources : {}
4191
+ });
4192
+ return partitionValidationErrors(resolved).surfaced;
4193
+ }
4194
+
4195
+ export { Api, CodeOptions, ColorOptions, DEFAULT_ERROR_MESSAGE, DateOptions, FileOptions, ImageEncodeOption, ImageOptions, JSON_ENTRIES_BATCH_MAX, ParentRef$1 as ParentRef, Patch, PatchBlock, PatchId, PatchJSON, RemirrorBr, RemirrorBulletList, RemirrorHeading, RemirrorImage, RemirrorJSON, RemirrorLinkMark, RemirrorListItem, RemirrorOrderedList, RemirrorParagraph, RemirrorText, RemirrorTextMark, RichTextOptions, SerializedArraySchema, SerializedBooleanSchema, SerializedCodeSchema, SerializedColorSchema, SerializedDateSchema, SerializedDateTimeSchema, SerializedFileSchema, SerializedImageSchema, SerializedKeyOfSchema, SerializedLiteralSchema, SerializedNumberSchema, SerializedObjectSchema, SerializedRecordSchema, SerializedRichTextSchema, SerializedRouteSchema, SerializedSchema, SerializedStringSchema, SerializedUnionSchema, SharedValConfig, VAL_AI_SESSION_STORAGE_KEY, VAL_CANVAS_MESSAGE, VAL_CANVAS_PARAM, VAL_CONFIG_SESSION_STORAGE_KEY, VAL_ENABLE_COOKIE_NAME, VAL_SESSION_COOKIE, VAL_STATE_COOKIE, VAL_THEME_SESSION_STORAGE_KEY, ValCommit, ValDeployment, buildDuplicatePatch, buildEmptyAtPathPatch, buildFileRefValue, buildImageGalleryPatch, buildRemoveImageGalleryEntryPatch, createRegExpFromPattern, createValClient, describeContainerAtPath, emptyOf, filterBlockingValidationErrors, filterRoutesByPatterns, findSimilar, getErrorMessageFromUnknownJson, getInlineImgInfo, getNextAppRouterSitemapTree, getNextAppRouterSourceFolder, getPatternFromModuleFilePath, getRoutesOf, getRoutesWithModulePaths, getSourceAt, isRemoteSchema, isValCanvasFrame, isValCanvasPageMessage, isValCanvasStudioMessage, levenshtein, parseRoutePattern, partitionValidationErrors, remirrorToRichTextSource, resolveSchemaSourceFixForError, resolveSchemaSourceFixes, resolveSerializedSchemaAtPath, richTextToRemirror, safeParsePatch, toolNames, urlOf, validateRoutePatterns, withValCanvasParam };