@jackuait/blok 0.4.1-beta.12 → 0.4.1-beta.14

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 { e as i } from "./blok-BU6NwVkN.mjs";
1
+ import { e as i } from "./blok-CqdUPkVp.mjs";
2
2
  const l = async (e, r) => {
3
3
  const n = (await import("./i18next-CugVlwWp.mjs")).default.createInstance(), s = {
4
4
  lng: e,
@@ -1,4 +1,4 @@
1
- import { t as f, q as i } from "./inline-tool-convert-CLUxkCe_.mjs";
1
+ import { t as f, q as i } from "./inline-tool-convert-CmxgdS16.mjs";
2
2
  const a = {
3
3
  wrapper: i(
4
4
  "fixed z-[2] bottom-5 left-5",
@@ -18,7 +18,7 @@ let nt = (o = 21) => {
18
18
  return t;
19
19
  };
20
20
  var ot = /* @__PURE__ */ ((o) => (o.VERBOSE = "VERBOSE", o.INFO = "INFO", o.WARN = "WARN", o.ERROR = "ERROR", o))(ot || {});
21
- const rt = () => "0.4.1-beta.12", Ct = {
21
+ const rt = () => "0.4.1-beta.14", Ct = {
22
22
  BACKSPACE: 8,
23
23
  TAB: 9,
24
24
  ENTER: 13,
package/dist/full.mjs CHANGED
@@ -10,10 +10,10 @@ var e = (a, l, o) => l in a ? n(a, l, { enumerable: !0, configurable: !0, writab
10
10
  d.call(l, o) && e(a, o, l[o]);
11
11
  return a;
12
12
  }, r = (a, l) => t(a, c(l));
13
- import { B as v, v as A } from "./chunks/blok-BU6NwVkN.mjs";
13
+ import { B as v, v as A } from "./chunks/blok-CqdUPkVp.mjs";
14
14
  import { List as p, Header as f, Paragraph as I, Link as k, Italic as u, Bold as B } from "./tools.mjs";
15
15
  import { defaultBlockTools as H, defaultInlineTools as P } from "./tools.mjs";
16
- import { D as _ } from "./chunks/inline-tool-convert-CLUxkCe_.mjs";
16
+ import { D as _ } from "./chunks/inline-tool-convert-CmxgdS16.mjs";
17
17
  const m = {
18
18
  paragraph: {
19
19
  class: I,
package/dist/tools.mjs CHANGED
@@ -2,7 +2,7 @@ var nt = Object.defineProperty, rt = Object.defineProperties;
2
2
  var st = Object.getOwnPropertyDescriptors;
3
3
  var K = Object.getOwnPropertySymbols;
4
4
  var ot = Object.prototype.hasOwnProperty, it = Object.prototype.propertyIsEnumerable;
5
- var U = (f, t, e) => t in f ? nt(f, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : f[t] = e, w = (f, t) => {
5
+ var U = (f, t, e) => t in f ? nt(f, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : f[t] = e, O = (f, t) => {
6
6
  for (var e in t || (t = {}))
7
7
  ot.call(t, e) && U(f, e, t[e]);
8
8
  if (K)
@@ -10,8 +10,8 @@ var U = (f, t, e) => t in f ? nt(f, t, { enumerable: !0, configurable: !0, writa
10
10
  it.call(t, e) && U(f, e, t[e]);
11
11
  return f;
12
12
  }, P = (f, t) => rt(f, st(t));
13
- import { t as x, D as m, a9 as et, aa as at, ab as lt, A as ct, ac as dt, ad as ut, ae as ht, af as ft, ag as pt, ah as mt, ai as G, aj as j, ak as $, f as A, al as gt, am as Et, S as H, P as Tt, an as Ct, l as At, J as yt } from "./chunks/inline-tool-convert-CLUxkCe_.mjs";
14
- import { a0 as Dt } from "./chunks/inline-tool-convert-CLUxkCe_.mjs";
13
+ import { t as x, D as m, a9 as et, aa as at, ab as lt, A as ct, ac as dt, ad as ut, ae as ht, af as ft, ag as pt, ah as mt, ai as G, aj as j, ak as $, f as A, al as gt, am as Et, S as H, P as Tt, an as Ct, l as At, J as yt } from "./chunks/inline-tool-convert-CmxgdS16.mjs";
14
+ import { a0 as Dt } from "./chunks/inline-tool-convert-CmxgdS16.mjs";
15
15
  const W = [
16
16
  "empty:before:pointer-events-none",
17
17
  "empty:before:text-gray-text",
@@ -555,15 +555,32 @@ const u = class u {
555
555
  })));
556
556
  }, this.api = n, this.readOnly = r, this._settings = e || {}, this._data = this.normalizeData(t), s && (this.blockId = s.id), this._data.style === "ordered" && this.api.events.on("block changed", this.handleBlockChanged);
557
557
  }
558
+ /**
559
+ * Legacy list item structure for backward compatibility
560
+ */
561
+ static isLegacyFormat(t) {
562
+ return typeof t == "object" && t !== null && "items" in t && Array.isArray(t.items);
563
+ }
558
564
  normalizeData(t) {
559
565
  var n;
560
566
  const e = this._settings.defaultStyle || "unordered";
561
- return !t || typeof t != "object" ? {
562
- text: "",
563
- style: e,
564
- checked: !1,
565
- depth: 0
566
- } : w({
567
+ if (!t || typeof t != "object")
568
+ return {
569
+ text: "",
570
+ style: e,
571
+ checked: !1,
572
+ depth: 0
573
+ };
574
+ if (u.isLegacyFormat(t)) {
575
+ const r = t.items[0], s = (r == null ? void 0 : r.content) || "", o = (r == null ? void 0 : r.checked) || !1;
576
+ return O({
577
+ text: s,
578
+ style: t.style || e,
579
+ checked: !!o,
580
+ depth: 0
581
+ }, t.start !== void 0 && t.start !== 1 ? { start: t.start } : {});
582
+ }
583
+ return O({
567
584
  text: t.text || "",
568
585
  style: t.style || e,
569
586
  checked: !!t.checked,
@@ -1155,7 +1172,7 @@ const u = class u {
1155
1172
  charCount: r.charCount,
1156
1173
  node: s,
1157
1174
  offset: e - r.charCount
1158
- } : P(w({}, r), {
1175
+ } : P(O({}, r), {
1159
1176
  charCount: r.charCount + o
1160
1177
  });
1161
1178
  },
@@ -1202,7 +1219,7 @@ const u = class u {
1202
1219
  this.syncContentFromDOM();
1203
1220
  const s = n + 1;
1204
1221
  this._data.depth = s;
1205
- const o = await this.api.blocks.update(this.blockId || "", P(w({}, this._data), {
1222
+ const o = await this.api.blocks.update(this.blockId || "", P(O({}, this._data), {
1206
1223
  depth: s
1207
1224
  }));
1208
1225
  this.setCaretToBlockContent(o);
@@ -1213,7 +1230,7 @@ const u = class u {
1213
1230
  this.syncContentFromDOM();
1214
1231
  const e = t - 1;
1215
1232
  this._data.depth = e;
1216
- const n = await this.api.blocks.update(this.blockId || "", P(w({}, this._data), {
1233
+ const n = await this.api.blocks.update(this.blockId || "", P(O({}, this._data), {
1217
1234
  depth: e
1218
1235
  }));
1219
1236
  this.setCaretToBlockContent(n);
@@ -2090,7 +2107,7 @@ const i = class i {
2090
2107
  return;
2091
2108
  const s = `strong[${i.DATA_ATTR_COLLAPSED_ACTIVE}="true"]`;
2092
2109
  r.querySelectorAll(s).forEach((l) => {
2093
- var k, I, F, z, q;
2110
+ var k, I, _, z, q;
2094
2111
  const d = l.getAttribute(i.DATA_ATTR_PREV_LENGTH), c = l.previousSibling;
2095
2112
  if (!d || !c || c.nodeType !== Node.TEXT_NODE)
2096
2113
  return;
@@ -2105,13 +2122,13 @@ const i = class i {
2105
2122
  const C = E.match(/^[\u00A0\s]+/);
2106
2123
  if (C && !l.hasAttribute(i.DATA_ATTR_LEADING_WHITESPACE) && l.setAttribute(i.DATA_ATTR_LEADING_WHITESPACE, C[0]), E.length === 0)
2107
2124
  return;
2108
- const T = (I = l.textContent) != null ? I : "", N = T + E, B = (F = l.getAttribute(i.DATA_ATTR_LEADING_WHITESPACE)) != null ? F : "", R = B.length > 0 && T.length === 0 && !N.startsWith(B) ? B + N : N, b = document.createTextNode(R);
2125
+ const T = (I = l.textContent) != null ? I : "", N = T + E, B = (_ = l.getAttribute(i.DATA_ATTR_LEADING_WHITESPACE)) != null ? _ : "", R = B.length > 0 && T.length === 0 && !N.startsWith(B) ? B + N : N, b = document.createTextNode(R);
2109
2126
  for (; l.firstChild; )
2110
2127
  l.removeChild(l.firstChild);
2111
2128
  if (l.appendChild(b), !(t != null && t.isCollapsed) || !i.isNodeWithin(t.focusNode, p))
2112
2129
  return;
2113
- const S = document.createRange(), _ = (q = (z = b.textContent) == null ? void 0 : z.length) != null ? q : 0;
2114
- S.setStart(b, _), S.collapse(!0), t.removeAllRanges(), t.addRange(S);
2130
+ const S = document.createRange(), F = (q = (z = b.textContent) == null ? void 0 : z.length) != null ? q : 0;
2131
+ S.setStart(b, F), S.collapse(!0), t.removeAllRanges(), t.addRange(S);
2115
2132
  });
2116
2133
  }
2117
2134
  /**
@@ -2320,7 +2337,7 @@ const i = class i {
2320
2337
  if (!r)
2321
2338
  return;
2322
2339
  r.querySelectorAll(`strong[${i.DATA_ATTR_COLLAPSED_LENGTH}]`).forEach((a) => {
2323
- var v, R, b, S, _;
2340
+ var v, R, b, S, F;
2324
2341
  const l = a, d = l.getAttribute(i.DATA_ATTR_COLLAPSED_LENGTH);
2325
2342
  if (!d)
2326
2343
  return;
@@ -2331,11 +2348,11 @@ const i = class i {
2331
2348
  if (B && T) {
2332
2349
  const k = N.slice(0, E), I = N.slice(E);
2333
2350
  T.textContent = k;
2334
- const F = document.createTextNode(I);
2335
- (b = l.parentNode) == null || b.insertBefore(F, l.nextSibling);
2351
+ const _ = document.createTextNode(I);
2352
+ (b = l.parentNode) == null || b.insertBefore(_, l.nextSibling);
2336
2353
  }
2337
2354
  if (B && l.removeAttribute(i.DATA_ATTR_PREV_LENGTH), t != null && t.isCollapsed && g && i.isNodeWithin(t.focusNode, l)) {
2338
- const k = document.createRange(), I = (_ = (S = g.textContent) == null ? void 0 : S.length) != null ? _ : 0;
2355
+ const k = document.createRange(), I = (F = (S = g.textContent) == null ? void 0 : S.length) != null ? F : 0;
2339
2356
  k.setStart(g, I), k.collapse(!0), t.removeAllRanges(), t.addRange(k);
2340
2357
  }
2341
2358
  p && l.removeAttribute(i.DATA_ATTR_COLLAPSED_LENGTH);
@@ -2602,7 +2619,7 @@ const i = class i {
2602
2619
  };
2603
2620
  i.isInline = !0, i.title = "Bold", i.titleKey = "bold", i.shortcutListenerRegistered = !1, i.selectionListenerRegistered = !1, i.inputListenerRegistered = !1, i.beforeInputListenerRegistered = !1, i.globalListenersInitialized = i.initializeGlobalListeners(), i.collapsedExitRecords = /* @__PURE__ */ new Set(), i.markerSequence = 0, i.isProcessingMutation = !1, i.DATA_ATTR_COLLAPSED_LENGTH = "data-blok-bold-collapsed-length", i.DATA_ATTR_COLLAPSED_ACTIVE = "data-blok-bold-collapsed-active", i.DATA_ATTR_PREV_LENGTH = "data-blok-bold-prev-length", i.DATA_ATTR_LEADING_WHITESPACE = "data-blok-bold-leading-ws", i.instances = /* @__PURE__ */ new Set(), i.pendingBoundaryCaretAdjustments = /* @__PURE__ */ new WeakSet(), i.shortcut = "CMD+B";
2604
2621
  let Z = i;
2605
- const O = class O {
2622
+ const D = class D {
2606
2623
  /**
2607
2624
  * Sanitizer Rule
2608
2625
  * Leave <i> and <em> tags
@@ -2873,9 +2890,9 @@ const O = class O {
2873
2890
  n.insertBefore(a, e.nextSibling), n.insertBefore(t, a);
2874
2891
  }
2875
2892
  };
2876
- O.isInline = !0, O.title = "Italic", O.titleKey = "italic", O.shortcut = "CMD+I";
2877
- let Q = O;
2878
- const D = class D {
2893
+ D.isInline = !0, D.title = "Italic", D.titleKey = "italic", D.shortcut = "CMD+I";
2894
+ let Q = D;
2895
+ const w = class w {
2879
2896
  /**
2880
2897
  * @param api - Blok API
2881
2898
  */
@@ -3102,8 +3119,8 @@ const D = class D {
3102
3119
  t && t.setAttribute(e, n ? "true" : "false");
3103
3120
  }
3104
3121
  };
3105
- D.isInline = !0, D.title = "Link", D.titleKey = "link", D.shortcut = "CMD+K";
3106
- let tt = D;
3122
+ w.isInline = !0, w.title = "Link", w.titleKey = "link", w.shortcut = "CMD+K";
3123
+ let tt = w;
3107
3124
  const vt = {
3108
3125
  paragraph: { preserveBlank: !0 },
3109
3126
  header: {},
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackuait/blok",
3
- "version": "0.4.1-beta.12",
3
+ "version": "0.4.1-beta.14",
4
4
  "description": "Blok — headless, highly extensible rich text editor built for developers who need to implement a block-based editing experience (similar to Notion) without building it from scratch",
5
5
  "module": "dist/blok.mjs",
6
6
  "types": "./types/index.d.ts",
@@ -96,14 +96,12 @@
96
96
  "verify:package:local": "npm pack && node scripts/verify-published-package.mjs --local",
97
97
  "verify:version": "node scripts/verify-version.mjs",
98
98
  "unpublish": "node scripts/unpublish-package.mjs",
99
- "bundle:track": "node scripts/track-bundle-size.mjs --verbose",
100
- "bundle:variants": "node scripts/build-bundle-variants.mjs --verbose",
101
- "bundle:trends": "node scripts/view-bundle-trends.mjs --trends",
102
- "bundle:history": "node scripts/view-bundle-trends.mjs",
103
99
  "perf:analyze": "node scripts/analyze-performance.mjs",
104
100
  "perf:compare": "node scripts/analyze-performance.mjs --baseline",
105
101
  "perf:dashboard": "node scripts/generate-performance-dashboard.mjs",
106
- "e2e:validate-categories": "node scripts/validate-test-categories.mjs"
102
+ "e2e:validate-categories": "node scripts/validate-test-categories.mjs",
103
+ "size": "size-limit",
104
+ "size:why": "size-limit --why"
107
105
  },
108
106
  "author": "JackUait",
109
107
  "contributors": [
@@ -123,6 +121,8 @@
123
121
  "@playwright/test": "1.57.0",
124
122
  "@semantic-release/changelog": "^6.0.3",
125
123
  "@semantic-release/git": "^10.0.1",
124
+ "@size-limit/esbuild-why": "^12.0.0",
125
+ "@size-limit/preset-small-lib": "^12.0.0",
126
126
  "@storybook/addon-a11y": "10.1.1",
127
127
  "@storybook/addon-vitest": "10.1.1",
128
128
  "@storybook/global": "5.0.0",
@@ -150,6 +150,7 @@
150
150
  "postcss": "8.5.6",
151
151
  "rollup-plugin-license": "3.6.0",
152
152
  "semantic-release": "^25.0.2",
153
+ "size-limit": "^12.0.0",
153
154
  "storybook": "10.1.1",
154
155
  "tailwindcss": "3",
155
156
  "tslint": "6.1.3",
@@ -163,5 +164,22 @@
163
164
  "dependencies": {
164
165
  "i18next": "^25.7.3",
165
166
  "nanoid": "^5.1.6"
166
- }
167
+ },
168
+ "size-limit": [
169
+ {
170
+ "name": "Minimum (core only)",
171
+ "path": "src/variants/blok-minimum.ts",
172
+ "limit": "300 KB"
173
+ },
174
+ {
175
+ "name": "Normal (with tools)",
176
+ "path": "src/blok.ts",
177
+ "limit": "300 KB"
178
+ },
179
+ {
180
+ "name": "Maximum (all locales)",
181
+ "path": "src/variants/blok-maximum.ts",
182
+ "limit": "300 KB"
183
+ }
184
+ ]
167
185
  }
@@ -789,6 +789,16 @@ export class UI extends Module<UINodes> {
789
789
  return;
790
790
  }
791
791
 
792
+ /**
793
+ * Close BlockSettings first if it's open, regardless of selection state.
794
+ * This prevents navigation mode from being enabled when the user closes block tunes with Escape.
795
+ */
796
+ if (this.Blok.BlockSettings.opened) {
797
+ this.Blok.BlockSettings.close();
798
+
799
+ return;
800
+ }
801
+
792
802
  /**
793
803
  * Clear blocks selection by ESC (but not when entering navigation mode)
794
804
  */
@@ -806,12 +816,6 @@ export class UI extends Module<UINodes> {
806
816
  return;
807
817
  }
808
818
 
809
- if (this.Blok.BlockSettings.opened) {
810
- this.Blok.BlockSettings.close();
811
-
812
- return;
813
- }
814
-
815
819
  /**
816
820
  * If a nested popover is open (like convert-to dropdown),
817
821
  * close only the nested popover, not the entire inline toolbar.
@@ -42,20 +42,26 @@ export interface DataFormatAnalysis {
42
42
  }
43
43
 
44
44
  /**
45
- * Recursively check if any list item has nested items
45
+ * Recursively check if any list item has nested items (for hasHierarchy flag)
46
46
  */
47
47
  const hasNestedListItems = (items: LegacyListItem[]): boolean => {
48
48
  return items.some(item => item.items !== undefined && item.items.length > 0);
49
49
  };
50
50
 
51
51
  /**
52
- * Check if a block contains legacy nested items structure
53
- * Currently supports: List tool with nested items[]
52
+ * Check if a block is in legacy list format (has items[] array with content field)
53
+ * Legacy format: { items: [{ content: "text" }], style: "unordered" }
54
+ * Flat format: { text: "text", style: "unordered" }
54
55
  */
55
- const hasNestedItems = (block: OutputBlockData): boolean => {
56
- const isListWithItems = block.type === 'list' && block.data?.items;
56
+ const isLegacyListBlock = (block: OutputBlockData): boolean => {
57
+ return block.type === 'list' && Array.isArray(block.data?.items);
58
+ };
57
59
 
58
- if (!isListWithItems) {
60
+ /**
61
+ * Check if a block contains nested hierarchy in its items
62
+ */
63
+ const hasNestedItems = (block: OutputBlockData): boolean => {
64
+ if (!isLegacyListBlock(block)) {
59
65
  return false;
60
66
  }
61
67
 
@@ -81,10 +87,14 @@ export const analyzeDataFormat = (blocks: OutputBlockData[]): DataFormatAnalysis
81
87
  return { format: 'hierarchical', hasHierarchy: true };
82
88
  }
83
89
 
84
- const foundLegacyNested = blocks.some(hasNestedItems);
90
+ // Check if any block uses legacy list format (items[] array)
91
+ const foundLegacyList = blocks.some(isLegacyListBlock);
92
+
93
+ if (foundLegacyList) {
94
+ // Check if there's actual nesting for the hasHierarchy flag
95
+ const hasNesting = blocks.some(hasNestedItems);
85
96
 
86
- if (foundLegacyNested) {
87
- return { format: 'legacy', hasHierarchy: true };
97
+ return { format: 'legacy', hasHierarchy: hasNesting };
88
98
  }
89
99
 
90
100
  return { format: 'flat', hasHierarchy: false };
@@ -109,37 +119,49 @@ const expandListItems = (
109
119
 
110
120
  childIds.push(itemId);
111
121
 
112
- // Recursively expand nested items first to get their IDs
113
- const nestedChildIds = item.items && item.items.length > 0
114
- ? expandListItems(item.items, itemId, depth + 1, style, undefined, tunes, blocks)
115
- : [];
116
-
117
122
  // Determine if we should include start (only for first root item of ordered lists)
118
123
  const includeStart = style === 'ordered' && depth === 0 && index === 0 && start !== undefined && start !== 1;
119
124
 
120
- // Create the list_item block
125
+ // Check if there are nested items (we'll get their IDs after pushing the parent)
126
+ const hasNestedItems = item.items && item.items.length > 0;
127
+
128
+ // Create the list block (flat model - each item is a separate 'list' block)
129
+ // We'll update with content IDs after processing children
121
130
  const itemBlock: OutputBlockData = {
122
131
  id: itemId,
123
- type: 'list_item',
132
+ type: 'list',
124
133
  data: {
125
134
  text: item.content,
126
135
  checked: item.checked,
127
136
  style,
137
+ ...(depth > 0 ? { depth } : {}),
128
138
  ...(includeStart ? { start } : {}),
129
139
  },
130
140
  ...(tunes !== undefined ? { tunes } : {}),
131
141
  ...(parentId !== undefined ? { parent: parentId } : {}),
132
- ...(nestedChildIds.length > 0 ? { content: nestedChildIds } : {}),
133
142
  };
134
143
 
144
+ // Push parent block first to maintain correct order (parent before children)
135
145
  blocks.push(itemBlock);
146
+
147
+ // Now recursively expand nested items (they will be added after the parent)
148
+ if (!hasNestedItems) {
149
+ return;
150
+ }
151
+
152
+ const nestedChildIds = expandListItems(item.items!, itemId, depth + 1, style, undefined, tunes, blocks);
153
+
154
+ // Update the parent block with content IDs (only if children exist)
155
+ if (nestedChildIds.length > 0) {
156
+ itemBlock.content = nestedChildIds;
157
+ }
136
158
  });
137
159
 
138
160
  return childIds;
139
161
  };
140
162
 
141
163
  /**
142
- * Expand a List block with nested items into flat list_item blocks
164
+ * Expand a List block with nested items into flat list blocks
143
165
  */
144
166
  const expandListToHierarchical = (
145
167
  listData: LegacyListData,
@@ -164,12 +186,7 @@ export const expandToHierarchical = (blocks: OutputBlockData[]): OutputBlockData
164
186
  const expandedBlocks: OutputBlockData[] = [];
165
187
 
166
188
  for (const block of blocks) {
167
- // Ensure block has an ID
168
- const blockId = block.id ?? generateBlockId();
169
-
170
- const isListBlock = block.type === 'list' && block.data?.items;
171
-
172
- if (isListBlock) {
189
+ if (isLegacyListBlock(block)) {
173
190
  // Expand List tool nested items to flat blocks
174
191
  const listData = block.data as LegacyListData;
175
192
  const expanded = expandListToHierarchical(listData, block.tunes);
@@ -179,7 +196,7 @@ export const expandToHierarchical = (blocks: OutputBlockData[]): OutputBlockData
179
196
  // Non-list blocks pass through unchanged (with guaranteed ID)
180
197
  expandedBlocks.push({
181
198
  ...block,
182
- id: blockId,
199
+ id: block.id ?? generateBlockId(),
183
200
  });
184
201
  }
185
202
  }
@@ -217,7 +234,7 @@ const collectChildItems = (
217
234
 
218
235
  for (const childId of contentIds) {
219
236
  const childBlock = blockMap.get(childId);
220
- const isListItem = childBlock && childBlock.type === 'list_item';
237
+ const isListItem = childBlock && childBlock.type === 'list';
221
238
 
222
239
  if (isListItem) {
223
240
  const items = collectListItems(childBlock, blockMap, processedIds);
@@ -269,7 +286,7 @@ const collectListItems = (
269
286
  };
270
287
 
271
288
  /**
272
- * Process a root list_item block and convert to a legacy List block
289
+ * Process a root list block (flat model) and convert to a legacy List block
273
290
  */
274
291
  const processRootListItem = (
275
292
  block: OutputBlockData,
@@ -294,6 +311,13 @@ const processRootListItem = (
294
311
  return listBlock;
295
312
  };
296
313
 
314
+ /**
315
+ * Check if a block is a flat-model list block (has 'text' field instead of 'items')
316
+ */
317
+ const isFlatModelListBlock = (block: OutputBlockData): boolean => {
318
+ return block.type === 'list' && block.data?.text !== undefined && block.data?.items === undefined;
319
+ };
320
+
297
321
  /**
298
322
  * Collapse hierarchical flat-with-references format back to legacy nested format
299
323
  * @param blocks - array of flat blocks with parent/content references
@@ -302,24 +326,21 @@ const processRootListItem = (
302
326
  export const collapseToLegacy = (blocks: OutputBlockData[]): OutputBlockData[] => {
303
327
  // Build a map of blocks by ID for quick lookup
304
328
  const blockMap = new Map<BlockId, OutputBlockData>();
305
- const listItemBlocks: OutputBlockData[] = [];
306
329
 
307
330
  for (const block of blocks) {
308
331
  if (block.id) {
309
332
  blockMap.set(block.id, block);
310
333
  }
311
-
312
- if (block.type === 'list_item') {
313
- listItemBlocks.push(block);
314
- }
315
334
  }
316
335
 
317
- // If no list_item blocks, just strip hierarchy fields and return
318
- if (listItemBlocks.length === 0) {
336
+ // If no flat-model list blocks, just strip hierarchy fields and return
337
+ const hasFlatListBlocks = blocks.some(isFlatModelListBlock);
338
+
339
+ if (!hasFlatListBlocks) {
319
340
  return blocks.map(stripHierarchyFields);
320
341
  }
321
342
 
322
- // Process blocks, converting root list_items to List blocks
343
+ // Process blocks, converting root flat-model list blocks to legacy List blocks
323
344
  const result: OutputBlockData[] = [];
324
345
  const processedIds = new Set<BlockId>();
325
346
 
@@ -330,8 +351,9 @@ export const collapseToLegacy = (blocks: OutputBlockData[]): OutputBlockData[] =
330
351
  continue;
331
352
  }
332
353
 
333
- const isRootListItem = block.type === 'list_item' && !block.parent;
334
- const isNonListItem = block.type !== 'list_item';
354
+ const isFlatListBlock = isFlatModelListBlock(block);
355
+ const isRootListItem = isFlatListBlock && !block.parent;
356
+ const isNonListItem = !isFlatListBlock;
335
357
 
336
358
  if (isRootListItem) {
337
359
  const listBlock = processRootListItem(block, blockMap, processedIds);
@@ -185,6 +185,13 @@ export class ListItem implements BlockTool {
185
185
 
186
186
  sanitize?: ToolSanitizerConfig | undefined;
187
187
 
188
+ /**
189
+ * Legacy list item structure for backward compatibility
190
+ */
191
+ private static isLegacyFormat(data: unknown): data is { items: Array<{ content: string; checked?: boolean }>, style?: ListItemStyle, start?: number } {
192
+ return typeof data === 'object' && data !== null && 'items' in data && Array.isArray((data as { items: unknown }).items);
193
+ }
194
+
188
195
  private normalizeData(data: ListItemData | Record<string, never>): ListItemData {
189
196
  const defaultStyle = this._settings.defaultStyle || 'unordered';
190
197
 
@@ -197,6 +204,22 @@ export class ListItem implements BlockTool {
197
204
  };
198
205
  }
199
206
 
207
+ // Handle legacy format with items[] array - extract first item's content
208
+ // This provides backward compatibility when legacy data is passed directly to the tool
209
+ if (ListItem.isLegacyFormat(data)) {
210
+ const firstItem = data.items[0];
211
+ const text = firstItem?.content || '';
212
+ const checked = firstItem?.checked || false;
213
+
214
+ return {
215
+ text,
216
+ style: data.style || defaultStyle,
217
+ checked: Boolean(checked),
218
+ depth: 0,
219
+ ...(data.start !== undefined && data.start !== 1 ? { start: data.start } : {}),
220
+ };
221
+ }
222
+
200
223
  return {
201
224
  text: data.text || '',
202
225
  style: data.style || defaultStyle,