@xtrable-ltd/nanoesis 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter-azure-blob.js +1 -1
- package/dist/chunk-EN5SMEWJ.js +1221 -0
- package/dist/{chunk-UZQ7UP2B.js → chunk-XO3CT6GL.js} +167 -15
- package/dist/editor-api.js +21 -1197
- package/dist/index.d.ts +139 -76
- package/dist/index.js +5 -1
- package/dist/mcp.d.ts +48 -0
- package/dist/mcp.js +76 -0
- package/editor/assets/{MigrationsPane-_FGonx4-.js → MigrationsPane-BYGqWBAA.js} +1 -1
- package/editor/assets/{TemplatesPane-Z6Bn69Hb.js → TemplatesPane-B5hn_v0Z.js} +168 -164
- package/editor/assets/{TemplatesPane-CiLiMCc8.css → TemplatesPane-D0gGehUt.css} +1 -1
- package/editor/assets/{cssMode-dkQrIPWx.js → cssMode-BbIf5k6I.js} +1 -1
- package/editor/assets/{freemarker2-DEqcFFWa.js → freemarker2-DoW0pSYV.js} +1 -1
- package/editor/assets/{handlebars-C6ojANWr.js → handlebars-DLlET-qc.js} +1 -1
- package/editor/assets/{html-BmiAmVUD.js → html-4khbqrhe.js} +1 -1
- package/editor/assets/{htmlMode-BBmUqToI.js → htmlMode-DblHkZ-k.js} +1 -1
- package/editor/assets/index-CkESQLMV.css +7 -0
- package/editor/assets/index-Do1drqEQ.js +138 -0
- package/editor/assets/{javascript-Cxm2TfJy.js → javascript-CgPO2Hmj.js} +1 -1
- package/editor/assets/{jsonMode-CW5012Hx.js → jsonMode-BrWh2436.js} +1 -1
- package/editor/assets/{liquid-DrS7ilHv.js → liquid-BsQJXwPT.js} +1 -1
- package/editor/assets/{mdx-CwdSU5o1.js → mdx-AO8t67gx.js} +1 -1
- package/editor/assets/{python-CALCR0yC.js → python-3w4sZj5c.js} +1 -1
- package/editor/assets/{razor-SVCo2LoM.js → razor-BFsvo06w.js} +1 -1
- package/editor/assets/{tsMode-CzXfTR_Q.js → tsMode-QrC4ERjp.js} +1 -1
- package/editor/assets/{typescript-CP0Ovrv7.js → typescript-BXJ3QLad.js} +1 -1
- package/editor/assets/{xml-B2yqloTa.js → xml-CxKYn1FP.js} +1 -1
- package/editor/assets/{yaml-DTLJhzgY.js → yaml-BmWLvF7Q.js} +1 -1
- package/editor/index.html +2 -2
- package/package.json +7 -1
- package/editor/assets/index-DEz8GUII.css +0 -7
- package/editor/assets/index-LtCzUHAw.js +0 -138
|
@@ -471,20 +471,58 @@ function freeze(field) {
|
|
|
471
471
|
}
|
|
472
472
|
|
|
473
473
|
// ../engine/src/template/stamp.ts
|
|
474
|
-
function
|
|
475
|
-
|
|
476
|
-
const
|
|
477
|
-
const
|
|
474
|
+
function isDestructiveTypeChange(from, to) {
|
|
475
|
+
if (from === to) return false;
|
|
476
|
+
const fromKind = valueKindOf(from);
|
|
477
|
+
const toKind = valueKindOf(to);
|
|
478
|
+
if (toKind === "html") return false;
|
|
479
|
+
if (fromKind === "html") return true;
|
|
480
|
+
if (from === "date" && to === "time" || from === "time" && to === "date") return true;
|
|
481
|
+
const isFreeText = (t) => t === "shorttext" || t === "text" || t === "code" || t === "email" || t === "phone";
|
|
482
|
+
if (isFreeText(from) && (to === "date" || to === "time")) return true;
|
|
483
|
+
if (fromKind === toKind) return false;
|
|
484
|
+
if (toKind === "text") return false;
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
function computeSchemaDelta(oldFields, newFields) {
|
|
488
|
+
const oldByName = new Map(oldFields.map((f) => [f.name, f]));
|
|
489
|
+
const newByName = new Map(newFields.map((f) => [f.name, f]));
|
|
478
490
|
const added = [];
|
|
479
|
-
|
|
480
|
-
|
|
491
|
+
const removed = [];
|
|
492
|
+
const typeChanged = [];
|
|
493
|
+
for (const [name, oldField] of oldByName) {
|
|
494
|
+
const newField = newByName.get(name);
|
|
495
|
+
if (newField === void 0) {
|
|
496
|
+
removed.push({ name, type: oldField.type });
|
|
497
|
+
} else if (newField.type !== oldField.type) {
|
|
498
|
+
typeChanged.push({
|
|
499
|
+
name,
|
|
500
|
+
from: oldField.type,
|
|
501
|
+
to: newField.type,
|
|
502
|
+
destructive: isDestructiveTypeChange(oldField.type, newField.type)
|
|
503
|
+
});
|
|
504
|
+
}
|
|
481
505
|
}
|
|
482
|
-
for (const name of
|
|
483
|
-
if (!
|
|
506
|
+
for (const [name, newField] of newByName) {
|
|
507
|
+
if (!oldByName.has(name)) added.push({ name, type: newField.type });
|
|
484
508
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
509
|
+
return {
|
|
510
|
+
added: added.sort((a, b) => a.name.localeCompare(b.name)),
|
|
511
|
+
removed: removed.sort((a, b) => a.name.localeCompare(b.name)),
|
|
512
|
+
typeChanged: typeChanged.sort((a, b) => a.name.localeCompare(b.name))
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
function detectStamp(oldSource, newSource, components = /* @__PURE__ */ new Map()) {
|
|
516
|
+
const oldFields = deriveFields(oldSource, components);
|
|
517
|
+
const newFields = deriveFields(newSource, components);
|
|
518
|
+
const delta = computeSchemaDelta(oldFields, newFields);
|
|
519
|
+
const destructive = delta.removed.length > 0 || delta.typeChanged.some((change) => change.destructive);
|
|
520
|
+
return {
|
|
521
|
+
destructive,
|
|
522
|
+
removedTokens: delta.removed.map((r) => r.name),
|
|
523
|
+
addedTokens: delta.added.map((a) => a.name),
|
|
524
|
+
typeChanged: delta.typeChanged
|
|
525
|
+
};
|
|
488
526
|
}
|
|
489
527
|
|
|
490
528
|
// ../engine/src/template/versions.ts
|
|
@@ -692,11 +730,15 @@ var IndexedStore = class {
|
|
|
692
730
|
const stampCandidate = stampTargetOf(target);
|
|
693
731
|
const previousBytes = stampCandidate !== void 0 ? await this.store.get(target) : void 0;
|
|
694
732
|
let stampDecision;
|
|
733
|
+
let schemaDelta;
|
|
695
734
|
if (stampCandidate !== void 0 && previousBytes !== void 0) {
|
|
696
735
|
const oldSource = new TextDecoder().decode(previousBytes);
|
|
697
736
|
const newSource = new TextDecoder().decode(bytes);
|
|
698
|
-
const
|
|
699
|
-
|
|
737
|
+
const oldFields = deriveFields(oldSource);
|
|
738
|
+
const newFields = deriveFields(newSource);
|
|
739
|
+
schemaDelta = computeSchemaDelta(oldFields, newFields);
|
|
740
|
+
const destructive = schemaDelta.removed.length > 0 || schemaDelta.typeChanged.some((change) => change.destructive);
|
|
741
|
+
if (destructive) {
|
|
700
742
|
const index2 = await this.loaded();
|
|
701
743
|
const siblings = index2.keys.filter((k) => k.startsWith(`${stampCandidate.dir}/`) && k.endsWith(".html")).map((k) => k.slice(stampCandidate.dir.length + 1, -".html".length));
|
|
702
744
|
const version = nextVersionNumber(siblings);
|
|
@@ -736,7 +778,8 @@ var IndexedStore = class {
|
|
|
736
778
|
this.index = nextIndex;
|
|
737
779
|
return {
|
|
738
780
|
...stamped !== void 0 && { stamped },
|
|
739
|
-
...stampIncomplete === true && { stampIncomplete: true }
|
|
781
|
+
...stampIncomplete === true && { stampIncomplete: true },
|
|
782
|
+
...schemaDelta !== void 0 && { schemaDelta }
|
|
740
783
|
};
|
|
741
784
|
});
|
|
742
785
|
}
|
|
@@ -2378,7 +2421,8 @@ function buildAuthoringReference(context = {}) {
|
|
|
2378
2421
|
annotationsSection(),
|
|
2379
2422
|
loopsSection(context),
|
|
2380
2423
|
componentsSection(context),
|
|
2381
|
-
shellSection()
|
|
2424
|
+
shellSection(),
|
|
2425
|
+
guardrailsSection()
|
|
2382
2426
|
]
|
|
2383
2427
|
};
|
|
2384
2428
|
}
|
|
@@ -2523,6 +2567,41 @@ function componentsSection(context) {
|
|
|
2523
2567
|
entries
|
|
2524
2568
|
};
|
|
2525
2569
|
}
|
|
2570
|
+
function guardrailsSection() {
|
|
2571
|
+
return {
|
|
2572
|
+
title: "Authoring guardrails for LLMs",
|
|
2573
|
+
intro: "Common pitfalls when authoring through MCP, where you can't see the editor form's shape change. Most stem from the same root: data-* annotations bind to the element they sit on, not to the token inside it.",
|
|
2574
|
+
entries: [
|
|
2575
|
+
{
|
|
2576
|
+
name: "data-* annotations are element-bound",
|
|
2577
|
+
summary: "data-type, data-required, data-field, data-minlength, and data-maxlength apply to the *element* that carries them \u2014 moving a token outside its annotated container silently strips those properties from the schema.",
|
|
2578
|
+
detail: 'Example: `<div data-type="richtext">{body}</div>` makes body a rich-text field. If you change it to `<div data-type="richtext"></div>{body}`, body drops back to plain shorttext. The compiler still substitutes the token at fragment root, but existing HTML content for body now renders escaped.',
|
|
2579
|
+
example: '<!-- WRONG (drops the annotation) -->\n<div data-type="richtext"></div>{body}\n\n<!-- RIGHT -->\n<div data-type="richtext">{body}</div>'
|
|
2580
|
+
},
|
|
2581
|
+
{
|
|
2582
|
+
name: "After write_file on a template, check `schemaDelta`",
|
|
2583
|
+
summary: "Every write to templates/*.html or components/*.html returns a `schemaDelta` describing added/removed/typeChanged fields. If `typeChanged` contains `destructive: true` entries or `removed` is non-empty, an auto-stamp has been written and authored content may need migration \u2014 surface to the user before further edits.",
|
|
2584
|
+
detail: "`schemaDelta` shape: `{added: [{name, type}], removed: [{name, type}], typeChanged: [{name, from, to, destructive}]}`. Empty arrays everywhere means the edit changed only whitespace, comments, or non-token markup \u2014 safe."
|
|
2585
|
+
},
|
|
2586
|
+
{
|
|
2587
|
+
name: "After rename_path on a template, search content for the old name",
|
|
2588
|
+
summary: 'Renaming `templates/article.html` to `templates/post.html` leaves every content item with `template: "article"` bound to a now-missing template. The validation gate will report these on publish, but the cheaper check is `list_pending_migrations` followed by editing the items\' `template` fields.'
|
|
2589
|
+
},
|
|
2590
|
+
{
|
|
2591
|
+
name: "After delete_path on a template, expect publish-blocking errors",
|
|
2592
|
+
summary: "Deleting a template that any item binds to will fail validation with `template-missing` per item. Use `list_pending_migrations` first to see who would be affected, and consider whether to rename items to a different template before deleting."
|
|
2593
|
+
},
|
|
2594
|
+
{
|
|
2595
|
+
name: "Read describe_template before editing a content item",
|
|
2596
|
+
summary: "A content item's `fields` object MUST match the bound template's schema (field names and value shapes). `describe_template` returns the field list with types so you build the right shape on the first try; mismatched keys become orphan fields the gate warns about."
|
|
2597
|
+
},
|
|
2598
|
+
{
|
|
2599
|
+
name: "richtext values are HTML, shorttext/text are plain text",
|
|
2600
|
+
summary: "For a `richtext` field, write the value as HTML (`<p>...</p>` etc). For `shorttext` or `text`, write plain text \u2014 any HTML you include is escaped at render time. If the gate emits a `content.type-drift` warning, a field's type changed destructively (commonly richtext \u2192 shorttext) and the old HTML content now renders as escaped text."
|
|
2601
|
+
}
|
|
2602
|
+
]
|
|
2603
|
+
};
|
|
2604
|
+
}
|
|
2526
2605
|
function shellSection() {
|
|
2527
2606
|
return {
|
|
2528
2607
|
title: "The document shell",
|
|
@@ -2637,6 +2716,65 @@ async function validateSite(source) {
|
|
|
2637
2716
|
expectedFieldsCache.set(templateName, fields);
|
|
2638
2717
|
return fields;
|
|
2639
2718
|
};
|
|
2719
|
+
const driftFieldsCache = /* @__PURE__ */ new Map();
|
|
2720
|
+
const driftFieldsFor = async (templateName) => {
|
|
2721
|
+
if (driftFieldsCache.has(templateName)) return driftFieldsCache.get(templateName) ?? null;
|
|
2722
|
+
const highest = await highestSnapshotName(templateName);
|
|
2723
|
+
if (highest === null) {
|
|
2724
|
+
driftFieldsCache.set(templateName, null);
|
|
2725
|
+
return null;
|
|
2726
|
+
}
|
|
2727
|
+
let snapshotHtml;
|
|
2728
|
+
try {
|
|
2729
|
+
snapshotHtml = await loadTemplate(source, highest);
|
|
2730
|
+
} catch {
|
|
2731
|
+
driftFieldsCache.set(templateName, null);
|
|
2732
|
+
return null;
|
|
2733
|
+
}
|
|
2734
|
+
const currentHtml = sourceCache.get(templateName);
|
|
2735
|
+
if (currentHtml === void 0) {
|
|
2736
|
+
driftFieldsCache.set(templateName, null);
|
|
2737
|
+
return null;
|
|
2738
|
+
}
|
|
2739
|
+
const delta = computeSchemaDelta(
|
|
2740
|
+
deriveFields(snapshotHtml, components),
|
|
2741
|
+
deriveFields(currentHtml, components)
|
|
2742
|
+
);
|
|
2743
|
+
const destructive = new Set(
|
|
2744
|
+
delta.typeChanged.filter((change) => change.destructive).map((change) => change.name)
|
|
2745
|
+
);
|
|
2746
|
+
driftFieldsCache.set(templateName, destructive);
|
|
2747
|
+
return destructive;
|
|
2748
|
+
};
|
|
2749
|
+
const snapshotNameCache = /* @__PURE__ */ new Map();
|
|
2750
|
+
const highestSnapshotName = async (baseName) => {
|
|
2751
|
+
if (snapshotNameCache.has(baseName)) return snapshotNameCache.get(baseName) ?? null;
|
|
2752
|
+
const slash = baseName.lastIndexOf("/");
|
|
2753
|
+
const dir = slash === -1 ? "templates" : `templates/${baseName.slice(0, slash)}`;
|
|
2754
|
+
const stem = slash === -1 ? baseName : baseName.slice(slash + 1);
|
|
2755
|
+
let entries;
|
|
2756
|
+
try {
|
|
2757
|
+
entries = await source.list(dir);
|
|
2758
|
+
} catch {
|
|
2759
|
+
snapshotNameCache.set(baseName, null);
|
|
2760
|
+
return null;
|
|
2761
|
+
}
|
|
2762
|
+
let highest = 0;
|
|
2763
|
+
let highestStem = null;
|
|
2764
|
+
const prefix = `${stem}@v`;
|
|
2765
|
+
for (const entry of entries) {
|
|
2766
|
+
if (entry.kind !== "file" || !entry.name.endsWith(".html")) continue;
|
|
2767
|
+
const entryStem = entry.name.slice(0, -".html".length);
|
|
2768
|
+
if (!entryStem.startsWith(prefix)) continue;
|
|
2769
|
+
const version = versionNumber(entryStem);
|
|
2770
|
+
if (version !== null && version > highest) {
|
|
2771
|
+
highest = version;
|
|
2772
|
+
highestStem = slash === -1 ? entryStem : `${baseName.slice(0, slash)}/${entryStem}`;
|
|
2773
|
+
}
|
|
2774
|
+
}
|
|
2775
|
+
snapshotNameCache.set(baseName, highestStem);
|
|
2776
|
+
return highestStem;
|
|
2777
|
+
};
|
|
2640
2778
|
const urlOwners = /* @__PURE__ */ new Map();
|
|
2641
2779
|
for (const { node, templateName } of published) {
|
|
2642
2780
|
const owners = urlOwners.get(urlForItem(node.path)) ?? [];
|
|
@@ -2674,6 +2812,18 @@ async function validateSite(source) {
|
|
|
2674
2812
|
node.path
|
|
2675
2813
|
);
|
|
2676
2814
|
}
|
|
2815
|
+
const driftFields = await driftFieldsFor(templateName);
|
|
2816
|
+
if (driftFields !== null && driftFields.size > 0) {
|
|
2817
|
+
const affected = itemFieldNames.filter((name) => driftFields.has(name));
|
|
2818
|
+
if (affected.length > 0) {
|
|
2819
|
+
add(
|
|
2820
|
+
"warning",
|
|
2821
|
+
"content.type-drift",
|
|
2822
|
+
`"${node.path}" has field${affected.length === 1 ? "" : "s"} whose type changed destructively in template "${templateName}": ${affected.map((f) => `"${f}"`).join(", ")}. The value will render under the new type (commonly HTML escaped as text). Open the Migrations workspace to migrate.`,
|
|
2823
|
+
node.path
|
|
2824
|
+
);
|
|
2825
|
+
}
|
|
2826
|
+
}
|
|
2677
2827
|
}
|
|
2678
2828
|
const scope = buildScope2(node.item);
|
|
2679
2829
|
for (const field of analysis.requiredFields) {
|
|
@@ -3110,6 +3260,8 @@ export {
|
|
|
3110
3260
|
findTokens,
|
|
3111
3261
|
wholeValueToken,
|
|
3112
3262
|
deriveFields,
|
|
3263
|
+
isDestructiveTypeChange,
|
|
3264
|
+
computeSchemaDelta,
|
|
3113
3265
|
detectStamp,
|
|
3114
3266
|
isVersionedTemplateName,
|
|
3115
3267
|
baseTemplateName,
|