@blokkli/editor 2.0.0-alpha.64 → 2.0.0-alpha.65

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.
Files changed (30) hide show
  1. package/dist/module.d.mts +2 -2
  2. package/dist/module.json +1 -1
  3. package/dist/module.mjs +48 -2
  4. package/dist/modules/agent/index.d.mts +1 -1
  5. package/dist/modules/agent/runtime/app/helpers/mutationResult.d.ts +12 -0
  6. package/dist/modules/agent/runtime/app/helpers/mutationResult.js +34 -0
  7. package/dist/modules/agent/runtime/app/providers/toolsProvider.js +13 -7
  8. package/dist/modules/agent/runtime/app/tools/add_paragraphs/index.d.ts +15 -4
  9. package/dist/modules/agent/runtime/app/tools/add_paragraphs/index.js +43 -15
  10. package/dist/modules/agent/runtime/server/server-tools/load_tools/index.d.ts +1 -3
  11. package/dist/modules/agent/runtime/server/server-tools/load_tools/index.js +3 -1
  12. package/dist/modules/charts/index.d.mts +1 -1
  13. package/dist/modules/drupal/index.d.mts +1 -1
  14. package/dist/modules/iframes/index.d.mts +1 -1
  15. package/dist/modules/index.d.mts +1 -1
  16. package/dist/modules/readability/index.d.mts +1 -1
  17. package/dist/modules/table-of-contents/index.d.mts +1 -1
  18. package/dist/runtime/editor/components/FlexTextarea/index.d.vue.ts +1 -1
  19. package/dist/runtime/editor/components/FlexTextarea/index.vue.d.ts +1 -1
  20. package/dist/runtime/editor/plugins/ItemAction/index.d.vue.ts +1 -1
  21. package/dist/runtime/editor/plugins/ItemAction/index.vue.d.ts +1 -1
  22. package/dist/runtime/editor/plugins/Sidebar/Detached/index.d.vue.ts +1 -1
  23. package/dist/runtime/editor/plugins/Sidebar/Detached/index.vue.d.ts +1 -1
  24. package/dist/runtime/editor/plugins/Sidebar/index.d.vue.ts +2 -2
  25. package/dist/runtime/editor/plugins/Sidebar/index.vue.d.ts +2 -2
  26. package/dist/runtime/editor/providers/state.d.ts +17 -1
  27. package/dist/runtime/editor/providers/state.js +14 -0
  28. package/dist/shared/{editor.BDyiQvbV.d.mts → editor.S5sA3rij.d.mts} +19 -1
  29. package/dist/types.d.mts +1 -1
  30. package/package.json +1 -1
package/dist/module.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { NuxtModule } from 'nuxt/schema';
2
- import { M as ModuleOptions } from './shared/editor.BDyiQvbV.mjs';
3
- export { a as ModuleHooks } from './shared/editor.BDyiQvbV.mjs';
2
+ import { M as ModuleOptions } from './shared/editor.S5sA3rij.mjs';
3
+ export { a as ModuleHooks } from './shared/editor.S5sA3rij.mjs';
4
4
  import 'consola';
5
5
  import '../dist/global/types/definitions.js';
6
6
  import '../dist/global/types/theme.js';
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blokkli/editor",
3
3
  "configKey": "blokkli",
4
- "version": "2.0.0-alpha.64",
4
+ "version": "2.0.0-alpha.65",
5
5
  "compatibility": {
6
6
  "nuxt": ">=3.15.0"
7
7
  },
package/dist/module.mjs CHANGED
@@ -19,7 +19,7 @@ import 'typescript';
19
19
  import 'oxc-walker';
20
20
 
21
21
  const name = "@blokkli/editor";
22
- const version = "2.0.0-alpha.64";
22
+ const version = "2.0.0-alpha.65";
23
23
 
24
24
  function validateOption(optionKey, option, icons) {
25
25
  const errors = [];
@@ -1203,6 +1203,11 @@ class ModuleHelper {
1203
1203
  * module and enabled sub-modules. Flushed by {@link applyBuildConfig}.
1204
1204
  */
1205
1205
  packageDependencies = /* @__PURE__ */ new Set();
1206
+ /**
1207
+ * npm packages that must resolve to a single copy across the whole app.
1208
+ * Flushed to Vite's resolve.dedupe by {@link applyBuildConfig}.
1209
+ */
1210
+ dedupedPackages = /* @__PURE__ */ new Set();
1206
1211
  isDev;
1207
1212
  isModuleBuild;
1208
1213
  isPrepare;
@@ -1279,11 +1284,28 @@ class ModuleHelper {
1279
1284
  this.packageDependencies.add(name);
1280
1285
  }
1281
1286
  }
1287
+ /**
1288
+ * Declare packages that must only ever exist once in the module graph.
1289
+ *
1290
+ * Libraries that rely on `instanceof` checks or module-level registries break
1291
+ * when a project's install tree ends up with two copies (e.g. a hoisted
1292
+ * version plus an older one nested under a transitive dependency). Forcing
1293
+ * resolution to a single copy makes the editor work regardless of how the
1294
+ * host project's package manager arranged node_modules.
1295
+ *
1296
+ * @param names - Bare package specifiers (e.g. 'prosemirror-model').
1297
+ */
1298
+ addDedupedPackage(...names) {
1299
+ for (const name of names) {
1300
+ this.dedupedPackages.add(name);
1301
+ }
1302
+ }
1282
1303
  /**
1283
1304
  * Apply collected build configuration to the Nuxt/Vite config.
1284
1305
  *
1285
1306
  * Called once by the core module after every module's setup has run, so it
1286
- * sees the full set of {@link addPackageDependency} registrations.
1307
+ * sees the full set of {@link addPackageDependency} and
1308
+ * {@link addDedupedPackage} registrations.
1287
1309
  */
1288
1310
  applyBuildConfig() {
1289
1311
  this.nuxt.options.vite.optimizeDeps ??= {};
@@ -1295,6 +1317,13 @@ class ModuleHelper {
1295
1317
  }
1296
1318
  include.push(name);
1297
1319
  }
1320
+ this.nuxt.options.vite.resolve ??= {};
1321
+ const dedupe = this.nuxt.options.vite.resolve.dedupe ??= [];
1322
+ for (const name of this.dedupedPackages) {
1323
+ if (!dedupe.includes(name)) {
1324
+ dedupe.push(name);
1325
+ }
1326
+ }
1298
1327
  }
1299
1328
  addAlias(name, path) {
1300
1329
  this.nuxt.options.alias[name] = path;
@@ -4695,6 +4724,23 @@ const module$1 = defineNuxtModule({
4695
4724
  "qrcode.vue",
4696
4725
  "twgl.js"
4697
4726
  );
4727
+ helper.addDedupedPackage(
4728
+ "@tiptap/core",
4729
+ "@tiptap/pm",
4730
+ "prosemirror-changeset",
4731
+ "prosemirror-commands",
4732
+ "prosemirror-dropcursor",
4733
+ "prosemirror-gapcursor",
4734
+ "prosemirror-history",
4735
+ "prosemirror-inputrules",
4736
+ "prosemirror-keymap",
4737
+ "prosemirror-model",
4738
+ "prosemirror-schema-list",
4739
+ "prosemirror-state",
4740
+ "prosemirror-tables",
4741
+ "prosemirror-transform",
4742
+ "prosemirror-view"
4743
+ );
4698
4744
  helper.applyBuildConfig();
4699
4745
  helper.addComponent("BlokkliField");
4700
4746
  helper.addComponent("BlokkliEditable");
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BDyiQvbV.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.S5sA3rij.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -1,4 +1,5 @@
1
1
  import type { BlokkliApp } from '#blokkli/editor/types/app';
2
+ import type { MutationResponseLike } from '#blokkli/editor/adapter';
2
3
  /**
3
4
  * Tree-shaped entry in the `newParagraphs` payload returned by mutation tools.
4
5
  *
@@ -27,6 +28,17 @@ export type NewParagraphNode = {
27
28
  * round-trips are needed.
28
29
  */
29
30
  export declare function buildNewParagraphsTree(newUuids: string[], app: BlokkliApp, itemEntityType: string): NewParagraphNode[];
31
+ /**
32
+ * Compose an agent-facing error message from a rejected mutation response.
33
+ *
34
+ * Backends return structured `violations` (field-level constraint failures)
35
+ * and/or plain `errors`. Violations carry the useful context (which field on
36
+ * which entity failed and why), so they're preferred; the generic `errors`
37
+ * list (e.g. Drupal's catch-all "Entity Violations") is only used as a
38
+ * fallback when no violations are present. The result is surfaced to the LLM
39
+ * so it can correct the mutation (e.g. supply the missing required field).
40
+ */
41
+ export declare function formatMutationFailure(response: MutationResponseLike<any> | undefined): string;
30
42
  /**
31
43
  * Count every node in a `newParagraphs` tree, including nested children.
32
44
  *
@@ -35,6 +35,40 @@ export function buildNewParagraphsTree(newUuids, app, itemEntityType) {
35
35
  }
36
36
  return roots;
37
37
  }
38
+ function violationLocation(violation) {
39
+ const parts = [];
40
+ if (violation.propertyPath) {
41
+ parts.push(violation.propertyPath);
42
+ }
43
+ if (violation.entityUuid) {
44
+ const entity = violation.entityType ?? "entity";
45
+ parts.push(
46
+ violation.propertyPath ? `(${entity} ${violation.entityUuid})` : `${entity} ${violation.entityUuid}`
47
+ );
48
+ }
49
+ return parts.join(" ");
50
+ }
51
+ export function formatMutationFailure(response) {
52
+ if (!response) {
53
+ return "The mutation failed: the backend returned no response.";
54
+ }
55
+ const violations = response.violations ?? [];
56
+ if (violations.length) {
57
+ const lines = violations.map((violation) => {
58
+ const location = violationLocation(violation);
59
+ return location ? `- ${location}: ${violation.message}` : `- ${violation.message}`;
60
+ });
61
+ return `The mutation was rejected by the backend:
62
+ ${lines.join("\n")}`;
63
+ }
64
+ const errors = (response.errors ?? []).filter(Boolean);
65
+ if (errors.length) {
66
+ const lines = errors.map((error) => `- ${error}`);
67
+ return `The mutation was rejected by the backend:
68
+ ${lines.join("\n")}`;
69
+ }
70
+ return "The mutation was rejected by the backend.";
71
+ }
38
72
  export function countNewParagraphs(nodes) {
39
73
  let total = 0;
40
74
  for (const node of nodes) {
@@ -11,7 +11,10 @@ import {
11
11
  asRecord,
12
12
  splitMeta
13
13
  } from "#blokkli/agent/app/helpers";
14
- import { buildNewParagraphsTree } from "#blokkli/agent/app/helpers/mutationResult";
14
+ import {
15
+ buildNewParagraphsTree,
16
+ formatMutationFailure
17
+ } from "#blokkli/agent/app/helpers/mutationResult";
15
18
  import { mcpTools } from "#blokkli-build/agent-client";
16
19
  import { itemEntityType } from "#blokkli-build/config";
17
20
  import { generateId } from "#blokkli/agent/app/helpers/id";
@@ -95,14 +98,19 @@ export default function toolsProvider({
95
98
  if (setLabel) setLabel(action.label);
96
99
  async function applyMutation() {
97
100
  const uuidsBefore = state.getAllUuids();
98
- await state.mutateWithLoadingState(() => action.apply(adapter));
101
+ const response = await state.applyMutationSilently(
102
+ () => action.apply(adapter)
103
+ );
104
+ if (!response || !response.success) {
105
+ return { ok: false, error: formatMutationFailure(response) };
106
+ }
99
107
  const newUuids = state.getAllUuids().filter((uuid) => !uuidsBefore.includes(uuid));
100
108
  const selectUuids = newUuids.length ? newUuids : action.affectedUuids || [];
101
109
  if (selectUuids.length) {
102
110
  app.eventBus.emit("select", selectUuids);
103
111
  app.eventBus.emit("scrollSelectionIntoView", {});
104
112
  }
105
- return newUuids;
113
+ return { ok: true, result: buildMutationResult(newUuids), meta: {} };
106
114
  }
107
115
  function buildMutationResult(newUuids) {
108
116
  const newParagraphs = action.type === "add" ? buildNewParagraphsTree(newUuids, app, itemEntityType) : [];
@@ -114,13 +122,11 @@ export default function toolsProvider({
114
122
  };
115
123
  }
116
124
  if (autoApprove.value || !toolDef.requiresApproval) {
117
- const newUuids = await applyMutation();
118
- return { ok: true, result: buildMutationResult(newUuids), meta: {} };
125
+ return applyMutation();
119
126
  }
120
127
  const approved = await waitForApproval(action);
121
128
  if (approved) {
122
- const newUuids = await applyMutation();
123
- return { ok: true, result: buildMutationResult(newUuids), meta: {} };
129
+ return applyMutation();
124
130
  }
125
131
  if (action.revert) action.revert();
126
132
  return { ok: true, result: { success: false, rejected: true }, meta: {} };
@@ -1,11 +1,22 @@
1
1
  import { z } from 'zod';
2
2
  type OptionValue = string | boolean | number | string[];
3
+ /**
4
+ * The accepted input shapes for a single content-field value, mirroring
5
+ * `contentFieldValueSchema`. Normalized to `CanonicalFieldValue` before use.
6
+ */
7
+ type ContentFieldInput = string | {
8
+ uri: string;
9
+ title?: string;
10
+ } | {
11
+ entityType: string;
12
+ entityId: string;
13
+ } | {
14
+ target_id: string;
15
+ target_type?: string;
16
+ };
3
17
  type BlockInput = {
4
18
  bundle: string;
5
- contentFields?: Record<string, string | {
6
- entityType: string;
7
- entityId: string;
8
- }>;
19
+ contentFields?: Record<string, ContentFieldInput>;
9
20
  options?: Record<string, OptionValue>;
10
21
  children?: Record<string, BlockInput[]>;
11
22
  };
@@ -17,23 +17,48 @@ import { validateFieldCardinality } from "../../helpers/validation.js";
17
17
  import { getFieldKey } from "#blokkli/helpers";
18
18
  import { optionValueToStorable } from "#blokkli/editor/helpers/options";
19
19
  import { countNewParagraphs } from "#blokkli/agent/app/helpers/mutationResult";
20
- const contentFieldValueSchema = z.union([
21
- z.string().describe(
22
- "Text value for plain/markup content fields, or a URL string (starting with http) for link content fields"
23
- ),
24
- z.object({
25
- entityType: z.string().describe('Entity type (e.g., "media", "node")'),
26
- entityId: z.string().describe("Entity ID")
27
- }).describe(
28
- "Entity reference for reference content fields (media, content references)"
29
- )
30
- ]);
20
+ const contentFieldValueSchema = z.union(
21
+ [
22
+ z.string().describe(
23
+ "Text for plain/markup fields, or a URL/link URI (https://\u2026, mailto:, tel:, internal:/path, entity:type/id) for link fields."
24
+ ),
25
+ z.object({
26
+ uri: z.string().describe("The link target: an absolute URL or a backend link URI."),
27
+ title: z.string().optional().describe(
28
+ "Optional link text. Accepted, but the URI is what gets stored \u2014 some backends do not persist the title yet."
29
+ )
30
+ }).describe("Link field value with an optional title."),
31
+ z.object({
32
+ entityType: z.string().describe('Entity type (e.g. "media", "node").'),
33
+ entityId: z.string().describe("Entity ID.")
34
+ }).describe(
35
+ "Entity reference for reference fields (media, content references), from search_media / search_content results."
36
+ ),
37
+ z.object({
38
+ target_id: z.string().describe("The referenced entity ID."),
39
+ target_type: z.string().optional().describe("The referenced entity type, if known.")
40
+ }).describe(
41
+ "Entity reference in the backend-native { target_id } shape (equivalent to { entityType, entityId })."
42
+ )
43
+ ],
44
+ {
45
+ error: "Content field value must be one of: a text/URL string; a link object { uri, title? }; or an entity reference { entityType, entityId } or { target_id, target_type? }."
46
+ }
47
+ );
31
48
  const contentFieldsSchema = z.record(
32
49
  z.string().describe("The content field name"),
33
50
  contentFieldValueSchema
34
51
  ).optional().describe(
35
52
  "Content field values to set on the new paragraph, keyed by field name. Use this to set text content and media/entity references in one step."
36
53
  );
54
+ function normalizeContentFieldValue(value) {
55
+ if (typeof value === "string") return value;
56
+ if ("uri" in value) return value.uri;
57
+ if ("target_id" in value) {
58
+ return { entityType: value.target_type ?? "", entityId: value.target_id };
59
+ }
60
+ return { entityType: value.entityType, entityId: value.entityId };
61
+ }
37
62
  const blockSchema = z.object({
38
63
  bundle: z.string().describe("The paragraph bundle to add"),
39
64
  contentFields: contentFieldsSchema,
@@ -54,7 +79,10 @@ export const paramsSchema = z.object({
54
79
  });
55
80
  function validateContentFields(ctx, block, path) {
56
81
  if (!block.contentFields) return void 0;
57
- for (const [fieldName, fieldValue] of Object.entries(block.contentFields)) {
82
+ for (const [fieldName, rawFieldValue] of Object.entries(
83
+ block.contentFields
84
+ )) {
85
+ const fieldValue = normalizeContentFieldValue(rawFieldValue);
58
86
  const editableConfig = ctx.app.types.editableFieldConfig.forName(
59
87
  ctx.itemEntityType,
60
88
  block.bundle,
@@ -76,7 +104,7 @@ function validateContentFields(ctx, block, path) {
76
104
  }
77
105
  if (droppableConfig) {
78
106
  if (typeof fieldValue === "string" && droppableConfig.type !== "link") {
79
- return `${path}: Field "${fieldName}" is a reference field and expects { entityType, entityId }, got a string.`;
107
+ return `${path}: Field "${fieldName}" is a reference field and expects { entityType, entityId } (or { target_id }), got a string.`;
80
108
  }
81
109
  }
82
110
  }
@@ -161,7 +189,7 @@ function buildEventBlocks(ctx, blocks, parentBundle) {
161
189
  const blockUuid = generateUUID();
162
190
  const values = block.contentFields ? Object.entries(block.contentFields).map(([fieldName, fieldValue]) => ({
163
191
  fieldName,
164
- fieldValue
192
+ fieldValue: normalizeContentFieldValue(fieldValue)
165
193
  })) : void 0;
166
194
  let options;
167
195
  if (block.options) {
@@ -229,7 +257,7 @@ function collectAllUuids(blocks) {
229
257
  export const resultSchema = mutationResultSchema;
230
258
  export default defineBlokkliAgentTool({
231
259
  name: "add_paragraphs",
232
- description: "Add one or more new paragraphs to the page. Supports nested structures via the `children` property \u2014 define entire paragraph trees in a single call. IMPORTANT: Always provide content field values (text, media/entity references) directly via contentFields, instead of adding empty paragraphs! For reference content fields (media), set the value to { entityType, entityId } from search_media results. NOTE: You can ONLY provide content fields, NOT paragraph fields! For nested paragraphs, use the `children` property keyed by paragraph field name. You can also set paragraph options inline via the `options` property (key-value pairs). The success result mirrors the input shape: each top-level entry in `newParagraphs` includes its own `children` keyed by paragraph field, so the structure round-trips and a nested child does NOT appear as a sibling \u2014 treat the tree as the source of truth instead of guessing from order.",
260
+ description: "Add one or more new paragraphs to the page. Supports nested structures via the `children` property \u2014 define entire paragraph trees in a single call. IMPORTANT: Always provide content field values (text, media/entity references) directly via contentFields, instead of adding empty paragraphs! For reference content fields (media), set the value to { entityType, entityId } from search_media results. For link fields, set a URL string or { uri, title }. NOTE: You can ONLY provide content fields, NOT paragraph fields! For nested paragraphs, use the `children` property keyed by paragraph field name. You can also set paragraph options inline via the `options` property (key-value pairs). The success result mirrors the input shape: each top-level entry in `newParagraphs` includes its own `children` keyed by paragraph field, so the structure round-trips and a nested child does NOT appear as a sibling \u2014 treat the tree as the source of truth instead of guessing from order.",
233
261
  category: "mutation",
234
262
  lazy: false,
235
263
  prunedSummary: (r) => r.success ? `added ${countNewParagraphs(r.newParagraphs ?? [])} paragraphs` : "rejected",
@@ -1,7 +1,5 @@
1
1
  import { z } from 'zod';
2
2
  declare const _default: import("..").ServerSideTool<z.ZodObject<{
3
- tools: z.ZodArray<z.ZodEnum<{
4
- [x: string]: string;
5
- }>>;
3
+ tools: z.ZodArray<z.ZodString>;
6
4
  }, z.core.$strip>>;
7
5
  export default _default;
@@ -5,7 +5,9 @@ export default defineServerSideTool({
5
5
  description: 'Load additional tools by name before using them. You must call this before using any tool listed under "Additional Tools" in the system prompt.',
6
6
  inputSchema(ctx) {
7
7
  return z.object({
8
- tools: z.array(z.enum(ctx.lazyToolNames)).describe("Tool names to activate")
8
+ tools: z.array(z.string()).describe(
9
+ ctx.lazyToolNames.length ? `Tool names to activate. Loadable tools: ${ctx.lazyToolNames.join(", ")}.` : "Tool names to activate."
10
+ )
9
11
  });
10
12
  },
11
13
  isAvailable(ctx) {
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BDyiQvbV.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.S5sA3rij.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BDyiQvbV.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.S5sA3rij.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BDyiQvbV.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.S5sA3rij.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -1,4 +1,4 @@
1
- export { b as Blokkli, B as BlokkliModule, d as defineBlokkliModule } from '../shared/editor.BDyiQvbV.mjs';
1
+ export { b as Blokkli, B as BlokkliModule, d as defineBlokkliModule } from '../shared/editor.S5sA3rij.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../dist/global/types/definitions.js';
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BDyiQvbV.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.S5sA3rij.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -1,4 +1,4 @@
1
- import { B as BlokkliModule } from '../../shared/editor.BDyiQvbV.mjs';
1
+ import { B as BlokkliModule } from '../../shared/editor.S5sA3rij.mjs';
2
2
  import 'nuxt/schema';
3
3
  import 'consola';
4
4
  import '../../../dist/global/types/definitions.js';
@@ -28,8 +28,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
28
28
  onSubmit?: (() => any) | undefined;
29
29
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
30
30
  }>, {
31
- minHeight: number;
32
31
  maxHeight: number;
32
+ minHeight: number;
33
33
  onBeforePaste: (data: ClipboardData) => boolean;
34
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
35
35
  declare const _default: typeof __VLS_export;
@@ -28,8 +28,8 @@ declare const __VLS_export: import("vue").DefineComponent<__VLS_PublicProps, {
28
28
  onSubmit?: (() => any) | undefined;
29
29
  "onUpdate:modelValue"?: ((value: string) => any) | undefined;
30
30
  }>, {
31
- minHeight: number;
32
31
  maxHeight: number;
32
+ minHeight: number;
33
33
  onBeforePaste: (data: ClipboardData) => boolean;
34
34
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
35
35
  declare const _default: typeof __VLS_export;
@@ -99,8 +99,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
99
99
  disabled: boolean | string;
100
100
  icon: BlokkliIcon;
101
101
  active: boolean;
102
- weight: number | string | "last";
103
102
  tourText: string;
103
+ weight: number | string | "last";
104
104
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
105
105
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
106
106
  declare const _default: typeof __VLS_export;
@@ -99,8 +99,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {}, {}, {},
99
99
  disabled: boolean | string;
100
100
  icon: BlokkliIcon;
101
101
  active: boolean;
102
- weight: number | string | "last";
103
102
  tourText: string;
103
+ weight: number | string | "last";
104
104
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
105
105
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
106
106
  declare const _default: typeof __VLS_export;
@@ -34,8 +34,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
34
34
  width: number;
35
35
  height: number;
36
36
  };
37
- minWidth: number;
38
37
  minHeight: number;
38
+ minWidth: number;
39
39
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
40
40
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
41
41
  declare const _default: typeof __VLS_export;
@@ -34,8 +34,8 @@ declare const __VLS_base: import("vue").DefineComponent<__VLS_Props, {
34
34
  width: number;
35
35
  height: number;
36
36
  };
37
- minWidth: number;
38
37
  minHeight: number;
38
+ minWidth: number;
39
39
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
40
40
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
41
41
  declare const _default: typeof __VLS_export;
@@ -200,9 +200,9 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
200
200
  width: number;
201
201
  height: number;
202
202
  };
203
- tourText: string;
204
- minWidth: number;
205
203
  minHeight: number;
204
+ minWidth: number;
205
+ tourText: string;
206
206
  region: SidebarRegion;
207
207
  tooltipTitle: string;
208
208
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
@@ -200,9 +200,9 @@ declare const __VLS_export: __VLS_WithSlots<import("vue").DefineComponent<{
200
200
  width: number;
201
201
  height: number;
202
202
  };
203
- tourText: string;
204
- minWidth: number;
205
203
  minHeight: number;
204
+ minWidth: number;
205
+ tourText: string;
206
206
  region: SidebarRegion;
207
207
  tooltipTitle: string;
208
208
  }, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>, {
@@ -1,5 +1,5 @@
1
1
  import { type Ref, type ComputedRef } from 'vue';
2
- import type { BlokkliAdapter, AdapterContext } from '../adapter/index.js';
2
+ import type { BlokkliAdapter, AdapterContext, MutationResponseLike } from '../adapter/index.js';
3
3
  import type { FieldListItem } from '#blokkli/types';
4
4
  import { type BlokkliEventBus } from '#blokkli/editor/events';
5
5
  import type { TextProvider } from './texts.js';
@@ -116,6 +116,22 @@ export type StateProvider = {
116
116
  * response, or `null`/`undefined` to no-op.
117
117
  */
118
118
  applyMutationState: (rawState: unknown) => void;
119
+ /**
120
+ * Run an adapter mutation with the loading lock, but return the raw response
121
+ * instead of a boolean and never emit an error toast.
122
+ *
123
+ * Like {@link mutateWithLoadingState} it locks the body for the duration of
124
+ * the call and applies any returned `state`, but it does NOT throw or show a
125
+ * global error message on failure. The caller receives the full
126
+ * {@link MutationResponseLike} and is responsible for surfacing violations —
127
+ * used by the agent tool runner, where the conversation UI already indicates
128
+ * that a tool errored and a global editor toast would be redundant.
129
+ *
130
+ * @param callback - Function that performs the mutation.
131
+ * @returns The raw mutation response, or `undefined` if no callback was
132
+ * provided or it resolved to no response.
133
+ */
134
+ applyMutationSilently: (callback: () => Promise<MutationResponseLike<any>> | undefined) => Promise<MutationResponseLike<any> | undefined>;
119
135
  /**
120
136
  * Current edit mode.
121
137
  *
@@ -319,6 +319,19 @@ export default async function(eventBus, adapter, context, $t, providerKey, permi
319
319
  unlockBody();
320
320
  return false;
321
321
  };
322
+ const applyMutationSilently = async (callback) => {
323
+ if (!callback) {
324
+ return void 0;
325
+ }
326
+ lockBody();
327
+ try {
328
+ const response = await callback();
329
+ applyMutationState(response?.state);
330
+ return response;
331
+ } finally {
332
+ unlockBody();
333
+ }
334
+ };
322
335
  async function loadState() {
323
336
  try {
324
337
  const state = await adapter.loadState();
@@ -401,6 +414,7 @@ export default async function(eventBus, adapter, context, $t, providerKey, permi
401
414
  lastChanged: readonly(lastChanged),
402
415
  mutateWithLoadingState,
403
416
  applyMutationState,
417
+ applyMutationSilently,
404
418
  editMode,
405
419
  canEdit,
406
420
  isLoading: readonly(isLoading),
@@ -264,6 +264,11 @@ declare class ModuleHelper implements ValidationInterface {
264
264
  * module and enabled sub-modules. Flushed by {@link applyBuildConfig}.
265
265
  */
266
266
  private packageDependencies;
267
+ /**
268
+ * npm packages that must resolve to a single copy across the whole app.
269
+ * Flushed to Vite's resolve.dedupe by {@link applyBuildConfig}.
270
+ */
271
+ private dedupedPackages;
267
272
  readonly isDev: boolean;
268
273
  readonly isModuleBuild: boolean;
269
274
  readonly isPrepare: boolean;
@@ -305,11 +310,24 @@ declare class ModuleHelper implements ValidationInterface {
305
310
  * @param names - Bare package specifiers (e.g. 'echarts', '@tiptap/core').
306
311
  */
307
312
  addPackageDependency(...names: string[]): void;
313
+ /**
314
+ * Declare packages that must only ever exist once in the module graph.
315
+ *
316
+ * Libraries that rely on `instanceof` checks or module-level registries break
317
+ * when a project's install tree ends up with two copies (e.g. a hoisted
318
+ * version plus an older one nested under a transitive dependency). Forcing
319
+ * resolution to a single copy makes the editor work regardless of how the
320
+ * host project's package manager arranged node_modules.
321
+ *
322
+ * @param names - Bare package specifiers (e.g. 'prosemirror-model').
323
+ */
324
+ addDedupedPackage(...names: string[]): void;
308
325
  /**
309
326
  * Apply collected build configuration to the Nuxt/Vite config.
310
327
  *
311
328
  * Called once by the core module after every module's setup has run, so it
312
- * sees the full set of {@link addPackageDependency} registrations.
329
+ * sees the full set of {@link addPackageDependency} and
330
+ * {@link addDedupedPackage} registrations.
313
331
  */
314
332
  applyBuildConfig(): void;
315
333
  addAlias(name: string, path: string): void;
package/dist/types.d.mts CHANGED
@@ -4,6 +4,6 @@ declare module '@nuxt/schema' {
4
4
  interface NuxtHooks extends ModuleHooks {}
5
5
  }
6
6
 
7
- export { type ModuleHooks } from './shared/editor.BDyiQvbV.mjs'
7
+ export { type ModuleHooks } from './shared/editor.S5sA3rij.mjs'
8
8
 
9
9
  export { type ModuleOptions, default } from './module.mjs'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blokkli/editor",
3
- "version": "2.0.0-alpha.64",
3
+ "version": "2.0.0-alpha.65",
4
4
  "description": "Interactive page building experience for Nuxt",
5
5
  "keywords": [
6
6
  "cms",