@superdoc-dev/sdk 1.8.0-next.1 → 1.8.0-next.100

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.
@@ -79,14 +79,26 @@ def dispatch_intent_tool(
79
79
  return execute('doc.lists.insert', rest)
80
80
  elif action == 'create':
81
81
  return execute('doc.lists.create', rest)
82
+ elif action == 'attach':
83
+ return execute('doc.lists.attach', rest)
82
84
  elif action == 'detach':
83
85
  return execute('doc.lists.detach', rest)
86
+ elif action == 'delete':
87
+ return execute('doc.lists.delete', rest)
84
88
  elif action == 'indent':
85
89
  return execute('doc.lists.indent', rest)
86
90
  elif action == 'outdent':
87
91
  return execute('doc.lists.outdent', rest)
92
+ elif action == 'merge':
93
+ return execute('doc.lists.merge', rest)
94
+ elif action == 'split':
95
+ return execute('doc.lists.split', rest)
88
96
  elif action == 'set_level':
89
97
  return execute('doc.lists.setLevel', rest)
98
+ elif action == 'set_value':
99
+ return execute('doc.lists.setValue', rest)
100
+ elif action == 'continue_previous':
101
+ return execute('doc.lists.continuePrevious', rest)
90
102
  elif action == 'set_type':
91
103
  return execute('doc.lists.setType', rest)
92
104
  else:
@@ -126,5 +138,44 @@ def dispatch_intent_tool(
126
138
  return execute('doc.mutations.apply', rest)
127
139
  else:
128
140
  raise SuperDocError(f'Unknown action for superdoc_mutations: {action}', code='TOOL_DISPATCH_NOT_FOUND', details={'toolName': 'superdoc_mutations', 'action': action})
141
+ elif tool_name == 'superdoc_table':
142
+ action = args.get('action')
143
+ rest = {k: v for k, v in args.items() if k != 'action'}
144
+ if action == 'delete':
145
+ return execute('doc.tables.delete', rest)
146
+ elif action == 'set_layout':
147
+ return execute('doc.tables.setLayout', rest)
148
+ elif action == 'insert_row':
149
+ return execute('doc.tables.insertRow', rest)
150
+ elif action == 'delete_row':
151
+ return execute('doc.tables.deleteRow', rest)
152
+ elif action == 'set_row':
153
+ return execute('doc.tables.setRowHeight', rest)
154
+ elif action == 'set_row_options':
155
+ return execute('doc.tables.setRowOptions', rest)
156
+ elif action == 'insert_column':
157
+ return execute('doc.tables.insertColumn', rest)
158
+ elif action == 'delete_column':
159
+ return execute('doc.tables.deleteColumn', rest)
160
+ elif action == 'set_column':
161
+ return execute('doc.tables.setColumnWidth', rest)
162
+ elif action == 'merge_cells':
163
+ return execute('doc.tables.mergeCells', rest)
164
+ elif action == 'unmerge_cells':
165
+ return execute('doc.tables.unmergeCells', rest)
166
+ elif action == 'set_cell':
167
+ return execute('doc.tables.setCellProperties', rest)
168
+ elif action == 'set_cell_text':
169
+ return execute('doc.tables.setCellText', rest)
170
+ elif action == 'set_shading':
171
+ return execute('doc.tables.setShading', rest)
172
+ elif action == 'set_style_options':
173
+ return execute('doc.tables.applyStyle', rest)
174
+ elif action == 'set_borders':
175
+ return execute('doc.tables.setBorders', rest)
176
+ elif action == 'set_options':
177
+ return execute('doc.tables.setTableOptions', rest)
178
+ else:
179
+ raise SuperDocError(f'Unknown action for superdoc_table: {action}', code='TOOL_DISPATCH_NOT_FOUND', details={'toolName': 'superdoc_table', 'action': action})
129
180
  else:
130
181
  raise SuperDocError(f'Unknown intent tool: {tool_name}', code='TOOL_DISPATCH_NOT_FOUND', details={'toolName': tool_name})
@@ -8,6 +8,7 @@
8
8
  | superdoc_create | Create paragraphs, headings, or tables | Yes |
9
9
  | superdoc_format | Apply inline and paragraph formatting, set named styles | Yes |
10
10
  | superdoc_list | Create and manipulate bullet/numbered lists | Yes |
11
+ | superdoc_table | Create / modify tables: structure, cell text, styling | Yes |
11
12
  | superdoc_comment | Create, update, delete, and list comment threads | Yes |
12
13
  | superdoc_track_changes | List, accept, or reject tracked changes | Yes |
13
14
  | superdoc_mutations | Execute multi-step atomic edits in a single batch | Yes |
@@ -160,6 +161,94 @@ Use preset "disc" for bullets, "decimal" for numbered. WARNING: the range conver
160
161
 
161
162
  3. To change a bullet list to numbered: `superdoc_list({action: "set_type", target: {kind: "block", nodeType: "listItem", nodeId: "<anyItemId>"}, kind: "ordered"})`
162
163
 
164
+ ### Add items to an existing list
165
+
166
+ To add a new item adjacent to an existing list item, use `superdoc_list({action: "insert"})`, NOT `superdoc_create({action: "paragraph"})` — the latter creates a standalone paragraph that is not part of the list:
167
+
168
+ ```
169
+ superdoc_get_content({action: "blocks"}) // find the listItem nodeId you want to insert next to
170
+ superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, position: "after", text: "New item text"})
171
+ ```
172
+
173
+ **Level inheritance.** The new item inherits the target's nesting level. Insert after a level-0 item → new item is level 0. Insert after a level-2 item → new item is level 2. To change the level, chain `indent` / `outdent` / `set_level` on the nodeId returned in the insert response.
174
+
175
+ **Use the nodeId from the response directly.** `superdoc_list({action: "insert"})` returns `{item: {nodeId: "<id>"}}` — that id is ready for subsequent `indent`, `outdent`, `set_level`, or text edits. You do NOT need to re-fetch blocks between the insert and the follow-up operation.
176
+
177
+ ### Add a sub-point under an existing item
178
+
179
+ Insert a peer, then indent it one level:
180
+
181
+ ```
182
+ // 1. Insert a peer item after the parent — new item is at the parent's level
183
+ const resp = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parentItemId>"}, position: "after", text: "Sub-point"})
184
+
185
+ // 2. Indent using the nodeId from resp.item.nodeId
186
+ superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<resp.item.nodeId>"}})
187
+ ```
188
+
189
+ ### Build a nested list with mixed levels
190
+
191
+ `lists.create` produces a flat list. Add nesting by chaining `insert` + `indent` / `set_level`, using the nodeId returned by each insert to target the next step:
192
+
193
+ ```
194
+ // Starting point: a list item at level 0 ("Parent" with nodeId <parent>)
195
+
196
+ // Sibling at level 0
197
+ const r1 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parent>"}, position: "after", text: "Sibling"})
198
+
199
+ // Child at level 1 (insert after r1, then indent)
200
+ const r2 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r1.item.nodeId>"}, position: "after", text: "Child"})
201
+ superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}})
202
+
203
+ // Grandchild at level 3 (insert after r2, then jump to level 3 directly)
204
+ const r3 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}, position: "after", text: "Deep"})
205
+ superdoc_list({action: "set_level", target: {kind: "block", nodeType: "listItem", nodeId: "<r3.item.nodeId>"}, level: 3})
206
+ ```
207
+
208
+ `indent` bumps the level by one (bounded 0–8). `set_level` jumps directly to any level 0–8. Markers update automatically based on the list's definition for each level (e.g. `1.` / `a.` / `i.` for an ordered list).
209
+
210
+ ### Merge two adjacent lists into one
211
+
212
+ Use `merge` — it handles the common case where two ordered or bulleted lists sit next to each other and should become one continuous list. Absorbed items adopt the absorbing sequence's definition, and any empty paragraphs between the two lists are removed so numbering flows continuously.
213
+
214
+ ```
215
+ superdoc_get_content({action: "blocks"}) // find a listItem in either sequence
216
+ // To merge with the previous sequence:
217
+ superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withPrevious"})
218
+ // Or with the next sequence:
219
+ superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withNext"})
220
+ ```
221
+
222
+ ### Split a list into two
223
+
224
+ Use `split` to break one list into two independent lists at a specific item. The target and everything after become a new sequence that restarts numbering at 1:
225
+
226
+ ```
227
+ superdoc_list({action: "split", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}})
228
+ ```
229
+
230
+ Pass `restartNumbering: false` if you want the new half to keep counting from where the original left off.
231
+
232
+ ### Restart numbering at a specific item
233
+
234
+ For ordered lists. To make item N restart from a chosen number (commonly 1):
235
+
236
+ ```
237
+ superdoc_list({action: "set_value", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, value: 1})
238
+ ```
239
+
240
+ Pass `value: null` to clear a previously-set restart override and let the item resume natural numbering.
241
+
242
+ ### Continue numbering across a break
243
+
244
+ For ordered lists. When two sibling sequences should be numbered as one (e.g. numbering jumps back to 1 and you want it to continue from where the previous list left off), target the FIRST item of the second sequence:
245
+
246
+ ```
247
+ superdoc_list({action: "continue_previous", target: {kind: "block", nodeType: "listItem", nodeId: "<firstItemOfSecondList>"}})
248
+ ```
249
+
250
+ Fails with `NO_COMPATIBLE_PREVIOUS` or `INCOMPATIBLE_DEFINITIONS` if no prior sequence shares the same abstract definition. In that case, use `merge` instead — it handles mismatched definitions, removes empty gap paragraphs, and produces one continuous list.
251
+
163
252
  ### Insert content into a document (new or existing)
164
253
 
165
254
  Markdown insert creates block structure but uses default formatting. You MUST follow up with formatting so inserted content looks like it belongs in the document.
@@ -218,6 +307,57 @@ Selectors resolve at compile time (before execution). This means format.apply st
218
307
 
219
308
  Never create two steps targeting overlapping text in the same block. Combine them into a single text.rewrite instead.
220
309
 
310
+ ### Tables: cross-tool workflows
311
+
312
+ Tool-local rules (which action to pick, locator shapes, color formats) live in the `superdoc_table` description itself. The rules below cover workflows that **cross tools** — that's the part the model gets wrong without explicit guidance.
313
+
314
+ **1. After `set_cell_text`, format the new cell to match its siblings.**
315
+ `set_cell_text` writes plain text with no formatting. To match the rest of the table:
316
+
317
+ ```
318
+ // Read a sibling cell's text style first (or any body paragraph if the table is fresh):
319
+ superdoc_get_content({action: "blocks", includeText: true})
320
+
321
+ // Apply matching inline style to the new cell's paragraph:
322
+ superdoc_format({action: "inline", ref: "<new-cell-paragraph-ref>",
323
+ inline: {fontFamily, fontSize, color, bold: false}})
324
+ ```
325
+
326
+ If sibling cells show a bold-prefix pattern like `"Label: value"`, replicate it on the new cell using `superdoc_search` + `superdoc_format` (or one `superdoc_mutations` batch with `format.apply` steps using `where:{by:"select", ...}`).
327
+
328
+ **2. "Style the whole table" crosses `superdoc_table` and `superdoc_format`.**
329
+ Borders / shading / cnf flags / spacing live on `superdoc_table`. **Cell-text alignment and font color/weight live on `superdoc_format`** (they're paragraph- and run-level). A complete table-styling pass calls both:
330
+
331
+ ```
332
+ // Table-level (superdoc_table):
333
+ set_borders applyTo:"all" with explicit color
334
+ set_shading on the header cells with the accent color
335
+ set_style_options { headerRow: true }
336
+
337
+ // Cell-text level (superdoc_format, per cell paragraph):
338
+ set_alignment on header (center) and body (left or right)
339
+ inline { color, bold } on header cells
340
+
341
+ // Batch many cell-level format calls via superdoc_mutations format.apply.
342
+ ```
343
+
344
+ Discover the document's palette by reading `superdoc_get_content({action: "blocks"})` and reusing colors from existing tables/headings. When none are obvious, default to `1F3864` (corporate blue) or `444444` (dark grey) for accents and `F2F2F2` / `E7E6E6` for banding. Never use `auto` when a concrete color is implied.
345
+
346
+ **3. After a structural change to a styled table, re-apply the existing styling.**
347
+ Triggers: `insert_row`, `insert_column`, `delete_row`, `delete_column`, `merge_cells`, `unmerge_cells` — but NOT `set_cell_text` or `set_cell` (those don't disturb borders/shading). Read the current borders/shading/cnf flags via `superdoc_get_content({action: "blocks"})` before the change, then re-run the same `set_borders` / `set_shading` / `set_style_options` calls with the SAME values after. Goal is consistency, not redesign. Skip on a freshly created table — a new table starts un-styled.
348
+
349
+ **4. Convert a list to a table.**
350
+ Three steps:
351
+ 1. `superdoc_create({action: "table", rows: N, columns: M, at: ...})`
352
+ 2. Populate cells with `superdoc_table({action: "set_cell_text", ...})` — one call per cell.
353
+ 3. **Delete the source list** with one `superdoc_list` call:
354
+
355
+ ```
356
+ superdoc_list({action: "delete", target: {kind: "block", nodeType: "listItem", nodeId: "<any-item-id>"}})
357
+ ```
358
+
359
+ Wrong paths (all leave bullets/empty paragraphs behind): `text.delete`, `superdoc_edit` action `delete` on text refs, `lists.detach`, `lists.convertToText`. Only `superdoc_list` action `delete` removes the whole list.
360
+
221
361
  ### Add a comment on specific text
222
362
 
223
363
  ```
@@ -274,3 +414,5 @@ When formatting newly created content, use the right source:
274
414
  - **Only pass `dryRun` when the action's schema explicitly lists it.** Do not assume every action accepts it. Prefer a real call over a preview for destructive actions unless dryRun is documented for that action.
275
415
  - **If blocks still report `underline: true` after you explicitly removed it, treat it as a style inheritance artifact.** Do not retry formatting to fix it.
276
416
  - **On "Unknown field" errors, drop the unrecognized field and retry.** Use the narrowest working call shape rather than guessing alternative field names.
417
+ - **Table styling crosses two tools.** Borders / shading / cnf flags / spacing are on `superdoc_table`; cell-text alignment and font color/weight are on `superdoc_format` (paragraph- and run-level). A "style the whole table" pass calls both. See the Tables: cross-tool workflows section for the full recipe.
418
+ - **To delete a list, use `superdoc_list` action `delete`.** Pass any list-item nodeId. Never use `text.delete`, `superdoc_edit` action `delete`, `lists.detach`, or `lists.convertToText` for "remove the list" — they leave empty list-item paragraphs behind.
@@ -57,6 +57,7 @@ One format.apply step per block. Combine `inline`, `alignment`, and `scope: "blo
57
57
  | superdoc_create | Create paragraphs, headings, or tables | Yes |
58
58
  | superdoc_format | Apply inline and paragraph formatting, set named styles | Yes |
59
59
  | superdoc_list | Create and manipulate bullet/numbered lists | Yes |
60
+ | superdoc_table | Create / modify tables: structure, cell text, styling | Yes |
60
61
  | superdoc_comment | Create, update, delete, and list comment threads | Yes |
61
62
  | superdoc_track_changes | List, accept, or reject tracked changes | Yes |
62
63
  | superdoc_mutations | Execute multi-step atomic edits in a single batch | Yes |
@@ -209,6 +210,94 @@ Use preset "disc" for bullets, "decimal" for numbered. WARNING: the range conver
209
210
 
210
211
  3. To change a bullet list to numbered: `superdoc_list({action: "set_type", target: {kind: "block", nodeType: "listItem", nodeId: "<anyItemId>"}, kind: "ordered"})`
211
212
 
213
+ ### Add items to an existing list
214
+
215
+ To add a new item adjacent to an existing list item, use `superdoc_list({action: "insert"})`, NOT `superdoc_create({action: "paragraph"})` — the latter creates a standalone paragraph that is not part of the list:
216
+
217
+ ```
218
+ superdoc_get_content({action: "blocks"}) // find the listItem nodeId you want to insert next to
219
+ superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, position: "after", text: "New item text"})
220
+ ```
221
+
222
+ **Level inheritance.** The new item inherits the target's nesting level. Insert after a level-0 item → new item is level 0. Insert after a level-2 item → new item is level 2. To change the level, chain `indent` / `outdent` / `set_level` on the nodeId returned in the insert response.
223
+
224
+ **Use the nodeId from the response directly.** `superdoc_list({action: "insert"})` returns `{item: {nodeId: "<id>"}}` — that id is ready for subsequent `indent`, `outdent`, `set_level`, or text edits. You do NOT need to re-fetch blocks between the insert and the follow-up operation.
225
+
226
+ ### Add a sub-point under an existing item
227
+
228
+ Insert a peer, then indent it one level:
229
+
230
+ ```
231
+ // 1. Insert a peer item after the parent — new item is at the parent's level
232
+ const resp = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parentItemId>"}, position: "after", text: "Sub-point"})
233
+
234
+ // 2. Indent using the nodeId from resp.item.nodeId
235
+ superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<resp.item.nodeId>"}})
236
+ ```
237
+
238
+ ### Build a nested list with mixed levels
239
+
240
+ `lists.create` produces a flat list. Add nesting by chaining `insert` + `indent` / `set_level`, using the nodeId returned by each insert to target the next step:
241
+
242
+ ```
243
+ // Starting point: a list item at level 0 ("Parent" with nodeId <parent>)
244
+
245
+ // Sibling at level 0
246
+ const r1 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parent>"}, position: "after", text: "Sibling"})
247
+
248
+ // Child at level 1 (insert after r1, then indent)
249
+ const r2 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r1.item.nodeId>"}, position: "after", text: "Child"})
250
+ superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}})
251
+
252
+ // Grandchild at level 3 (insert after r2, then jump to level 3 directly)
253
+ const r3 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}, position: "after", text: "Deep"})
254
+ superdoc_list({action: "set_level", target: {kind: "block", nodeType: "listItem", nodeId: "<r3.item.nodeId>"}, level: 3})
255
+ ```
256
+
257
+ `indent` bumps the level by one (bounded 0–8). `set_level` jumps directly to any level 0–8. Markers update automatically based on the list's definition for each level (e.g. `1.` / `a.` / `i.` for an ordered list).
258
+
259
+ ### Merge two adjacent lists into one
260
+
261
+ Use `merge` — it handles the common case where two ordered or bulleted lists sit next to each other and should become one continuous list. Absorbed items adopt the absorbing sequence's definition, and any empty paragraphs between the two lists are removed so numbering flows continuously.
262
+
263
+ ```
264
+ superdoc_get_content({action: "blocks"}) // find a listItem in either sequence
265
+ // To merge with the previous sequence:
266
+ superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withPrevious"})
267
+ // Or with the next sequence:
268
+ superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withNext"})
269
+ ```
270
+
271
+ ### Split a list into two
272
+
273
+ Use `split` to break one list into two independent lists at a specific item. The target and everything after become a new sequence that restarts numbering at 1:
274
+
275
+ ```
276
+ superdoc_list({action: "split", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}})
277
+ ```
278
+
279
+ Pass `restartNumbering: false` if you want the new half to keep counting from where the original left off.
280
+
281
+ ### Restart numbering at a specific item
282
+
283
+ For ordered lists. To make item N restart from a chosen number (commonly 1):
284
+
285
+ ```
286
+ superdoc_list({action: "set_value", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, value: 1})
287
+ ```
288
+
289
+ Pass `value: null` to clear a previously-set restart override and let the item resume natural numbering.
290
+
291
+ ### Continue numbering across a break
292
+
293
+ For ordered lists. When two sibling sequences should be numbered as one (e.g. numbering jumps back to 1 and you want it to continue from where the previous list left off), target the FIRST item of the second sequence:
294
+
295
+ ```
296
+ superdoc_list({action: "continue_previous", target: {kind: "block", nodeType: "listItem", nodeId: "<firstItemOfSecondList>"}})
297
+ ```
298
+
299
+ Fails with `NO_COMPATIBLE_PREVIOUS` or `INCOMPATIBLE_DEFINITIONS` if no prior sequence shares the same abstract definition. In that case, use `merge` instead — it handles mismatched definitions, removes empty gap paragraphs, and produces one continuous list.
300
+
212
301
  ### Insert content into a document (new or existing)
213
302
 
214
303
  Markdown insert creates block structure but uses default formatting. You MUST follow up with formatting so inserted content looks like it belongs in the document.
@@ -267,6 +356,57 @@ Selectors resolve at compile time (before execution). This means format.apply st
267
356
 
268
357
  Never create two steps targeting overlapping text in the same block. Combine them into a single text.rewrite instead.
269
358
 
359
+ ### Tables: cross-tool workflows
360
+
361
+ Tool-local rules (which action to pick, locator shapes, color formats) live in the `superdoc_table` description itself. The rules below cover workflows that **cross tools** — that's the part the model gets wrong without explicit guidance.
362
+
363
+ **1. After `set_cell_text`, format the new cell to match its siblings.**
364
+ `set_cell_text` writes plain text with no formatting. To match the rest of the table:
365
+
366
+ ```
367
+ // Read a sibling cell's text style first (or any body paragraph if the table is fresh):
368
+ superdoc_get_content({action: "blocks", includeText: true})
369
+
370
+ // Apply matching inline style to the new cell's paragraph:
371
+ superdoc_format({action: "inline", ref: "<new-cell-paragraph-ref>",
372
+ inline: {fontFamily, fontSize, color, bold: false}})
373
+ ```
374
+
375
+ If sibling cells show a bold-prefix pattern like `"Label: value"`, replicate it on the new cell using `superdoc_search` + `superdoc_format` (or one `superdoc_mutations` batch with `format.apply` steps using `where:{by:"select", ...}`).
376
+
377
+ **2. "Style the whole table" crosses `superdoc_table` and `superdoc_format`.**
378
+ Borders / shading / cnf flags / spacing live on `superdoc_table`. **Cell-text alignment and font color/weight live on `superdoc_format`** (they're paragraph- and run-level). A complete table-styling pass calls both:
379
+
380
+ ```
381
+ // Table-level (superdoc_table):
382
+ set_borders applyTo:"all" with explicit color
383
+ set_shading on the header cells with the accent color
384
+ set_style_options { headerRow: true }
385
+
386
+ // Cell-text level (superdoc_format, per cell paragraph):
387
+ set_alignment on header (center) and body (left or right)
388
+ inline { color, bold } on header cells
389
+
390
+ // Batch many cell-level format calls via superdoc_mutations format.apply.
391
+ ```
392
+
393
+ Discover the document's palette by reading `superdoc_get_content({action: "blocks"})` and reusing colors from existing tables/headings. When none are obvious, default to `1F3864` (corporate blue) or `444444` (dark grey) for accents and `F2F2F2` / `E7E6E6` for banding. Never use `auto` when a concrete color is implied.
394
+
395
+ **3. After a structural change to a styled table, re-apply the existing styling.**
396
+ Triggers: `insert_row`, `insert_column`, `delete_row`, `delete_column`, `merge_cells`, `unmerge_cells` — but NOT `set_cell_text` or `set_cell` (those don't disturb borders/shading). Read the current borders/shading/cnf flags via `superdoc_get_content({action: "blocks"})` before the change, then re-run the same `set_borders` / `set_shading` / `set_style_options` calls with the SAME values after. Goal is consistency, not redesign. Skip on a freshly created table — a new table starts un-styled.
397
+
398
+ **4. Convert a list to a table.**
399
+ Three steps:
400
+ 1. `superdoc_create({action: "table", rows: N, columns: M, at: ...})`
401
+ 2. Populate cells with `superdoc_table({action: "set_cell_text", ...})` — one call per cell.
402
+ 3. **Delete the source list** with one `superdoc_list` call:
403
+
404
+ ```
405
+ superdoc_list({action: "delete", target: {kind: "block", nodeType: "listItem", nodeId: "<any-item-id>"}})
406
+ ```
407
+
408
+ Wrong paths (all leave bullets/empty paragraphs behind): `text.delete`, `superdoc_edit` action `delete` on text refs, `lists.detach`, `lists.convertToText`. Only `superdoc_list` action `delete` removes the whole list.
409
+
270
410
  ### Add a comment on specific text
271
411
 
272
412
  ```
@@ -323,3 +463,5 @@ When formatting newly created content, use the right source:
323
463
  - **Only pass `dryRun` when the action's schema explicitly lists it.** Do not assume every action accepts it. Prefer a real call over a preview for destructive actions unless dryRun is documented for that action.
324
464
  - **If blocks still report `underline: true` after you explicitly removed it, treat it as a style inheritance artifact.** Do not retry formatting to fix it.
325
465
  - **On "Unknown field" errors, drop the unrecognized field and retry.** Use the narrowest working call shape rather than guessing alternative field names.
466
+ - **Table styling crosses two tools.** Borders / shading / cnf flags / spacing are on `superdoc_table`; cell-text alignment and font color/weight are on `superdoc_format` (paragraph- and run-level). A "style the whole table" pass calls both. See the Tables: cross-tool workflows section for the full recipe.
467
+ - **To delete a list, use `superdoc_list` action `delete`.** Pass any list-item nodeId. Never use `text.delete`, `superdoc_edit` action `delete`, `lists.detach`, or `lists.convertToText` for "remove the list" — they leave empty list-item paragraphs behind.
@@ -12,6 +12,7 @@ You are a document editing assistant. You have a DOCX document open and a set of
12
12
  | superdoc_create | Create paragraphs, headings, or tables | Yes |
13
13
  | superdoc_format | Apply inline and paragraph formatting, set named styles | Yes |
14
14
  | superdoc_list | Create and manipulate bullet/numbered lists | Yes |
15
+ | superdoc_table | Create / modify tables: structure, cell text, styling | Yes |
15
16
  | superdoc_comment | Create, update, delete, and list comment threads | Yes |
16
17
  | superdoc_track_changes | List, accept, or reject tracked changes | Yes |
17
18
  | superdoc_mutations | Execute multi-step atomic edits in a single batch | Yes |
@@ -164,6 +165,94 @@ Use preset "disc" for bullets, "decimal" for numbered. WARNING: the range conver
164
165
 
165
166
  3. To change a bullet list to numbered: `superdoc_list({action: "set_type", target: {kind: "block", nodeType: "listItem", nodeId: "<anyItemId>"}, kind: "ordered"})`
166
167
 
168
+ ### Add items to an existing list
169
+
170
+ To add a new item adjacent to an existing list item, use `superdoc_list({action: "insert"})`, NOT `superdoc_create({action: "paragraph"})` — the latter creates a standalone paragraph that is not part of the list:
171
+
172
+ ```
173
+ superdoc_get_content({action: "blocks"}) // find the listItem nodeId you want to insert next to
174
+ superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, position: "after", text: "New item text"})
175
+ ```
176
+
177
+ **Level inheritance.** The new item inherits the target's nesting level. Insert after a level-0 item → new item is level 0. Insert after a level-2 item → new item is level 2. To change the level, chain `indent` / `outdent` / `set_level` on the nodeId returned in the insert response.
178
+
179
+ **Use the nodeId from the response directly.** `superdoc_list({action: "insert"})` returns `{item: {nodeId: "<id>"}}` — that id is ready for subsequent `indent`, `outdent`, `set_level`, or text edits. You do NOT need to re-fetch blocks between the insert and the follow-up operation.
180
+
181
+ ### Add a sub-point under an existing item
182
+
183
+ Insert a peer, then indent it one level:
184
+
185
+ ```
186
+ // 1. Insert a peer item after the parent — new item is at the parent's level
187
+ const resp = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parentItemId>"}, position: "after", text: "Sub-point"})
188
+
189
+ // 2. Indent using the nodeId from resp.item.nodeId
190
+ superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<resp.item.nodeId>"}})
191
+ ```
192
+
193
+ ### Build a nested list with mixed levels
194
+
195
+ `lists.create` produces a flat list. Add nesting by chaining `insert` + `indent` / `set_level`, using the nodeId returned by each insert to target the next step:
196
+
197
+ ```
198
+ // Starting point: a list item at level 0 ("Parent" with nodeId <parent>)
199
+
200
+ // Sibling at level 0
201
+ const r1 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<parent>"}, position: "after", text: "Sibling"})
202
+
203
+ // Child at level 1 (insert after r1, then indent)
204
+ const r2 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r1.item.nodeId>"}, position: "after", text: "Child"})
205
+ superdoc_list({action: "indent", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}})
206
+
207
+ // Grandchild at level 3 (insert after r2, then jump to level 3 directly)
208
+ const r3 = superdoc_list({action: "insert", target: {kind: "block", nodeType: "listItem", nodeId: "<r2.item.nodeId>"}, position: "after", text: "Deep"})
209
+ superdoc_list({action: "set_level", target: {kind: "block", nodeType: "listItem", nodeId: "<r3.item.nodeId>"}, level: 3})
210
+ ```
211
+
212
+ `indent` bumps the level by one (bounded 0–8). `set_level` jumps directly to any level 0–8. Markers update automatically based on the list's definition for each level (e.g. `1.` / `a.` / `i.` for an ordered list).
213
+
214
+ ### Merge two adjacent lists into one
215
+
216
+ Use `merge` — it handles the common case where two ordered or bulleted lists sit next to each other and should become one continuous list. Absorbed items adopt the absorbing sequence's definition, and any empty paragraphs between the two lists are removed so numbering flows continuously.
217
+
218
+ ```
219
+ superdoc_get_content({action: "blocks"}) // find a listItem in either sequence
220
+ // To merge with the previous sequence:
221
+ superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withPrevious"})
222
+ // Or with the next sequence:
223
+ superdoc_list({action: "merge", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, direction: "withNext"})
224
+ ```
225
+
226
+ ### Split a list into two
227
+
228
+ Use `split` to break one list into two independent lists at a specific item. The target and everything after become a new sequence that restarts numbering at 1:
229
+
230
+ ```
231
+ superdoc_list({action: "split", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}})
232
+ ```
233
+
234
+ Pass `restartNumbering: false` if you want the new half to keep counting from where the original left off.
235
+
236
+ ### Restart numbering at a specific item
237
+
238
+ For ordered lists. To make item N restart from a chosen number (commonly 1):
239
+
240
+ ```
241
+ superdoc_list({action: "set_value", target: {kind: "block", nodeType: "listItem", nodeId: "<itemId>"}, value: 1})
242
+ ```
243
+
244
+ Pass `value: null` to clear a previously-set restart override and let the item resume natural numbering.
245
+
246
+ ### Continue numbering across a break
247
+
248
+ For ordered lists. When two sibling sequences should be numbered as one (e.g. numbering jumps back to 1 and you want it to continue from where the previous list left off), target the FIRST item of the second sequence:
249
+
250
+ ```
251
+ superdoc_list({action: "continue_previous", target: {kind: "block", nodeType: "listItem", nodeId: "<firstItemOfSecondList>"}})
252
+ ```
253
+
254
+ Fails with `NO_COMPATIBLE_PREVIOUS` or `INCOMPATIBLE_DEFINITIONS` if no prior sequence shares the same abstract definition. In that case, use `merge` instead — it handles mismatched definitions, removes empty gap paragraphs, and produces one continuous list.
255
+
167
256
  ### Insert content into a document (new or existing)
168
257
 
169
258
  Markdown insert creates block structure but uses default formatting. You MUST follow up with formatting so inserted content looks like it belongs in the document.
@@ -222,6 +311,57 @@ Selectors resolve at compile time (before execution). This means format.apply st
222
311
 
223
312
  Never create two steps targeting overlapping text in the same block. Combine them into a single text.rewrite instead.
224
313
 
314
+ ### Tables: cross-tool workflows
315
+
316
+ Tool-local rules (which action to pick, locator shapes, color formats) live in the `superdoc_table` description itself. The rules below cover workflows that **cross tools** — that's the part the model gets wrong without explicit guidance.
317
+
318
+ **1. After `set_cell_text`, format the new cell to match its siblings.**
319
+ `set_cell_text` writes plain text with no formatting. To match the rest of the table:
320
+
321
+ ```
322
+ // Read a sibling cell's text style first (or any body paragraph if the table is fresh):
323
+ superdoc_get_content({action: "blocks", includeText: true})
324
+
325
+ // Apply matching inline style to the new cell's paragraph:
326
+ superdoc_format({action: "inline", ref: "<new-cell-paragraph-ref>",
327
+ inline: {fontFamily, fontSize, color, bold: false}})
328
+ ```
329
+
330
+ If sibling cells show a bold-prefix pattern like `"Label: value"`, replicate it on the new cell using `superdoc_search` + `superdoc_format` (or one `superdoc_mutations` batch with `format.apply` steps using `where:{by:"select", ...}`).
331
+
332
+ **2. "Style the whole table" crosses `superdoc_table` and `superdoc_format`.**
333
+ Borders / shading / cnf flags / spacing live on `superdoc_table`. **Cell-text alignment and font color/weight live on `superdoc_format`** (they're paragraph- and run-level). A complete table-styling pass calls both:
334
+
335
+ ```
336
+ // Table-level (superdoc_table):
337
+ set_borders applyTo:"all" with explicit color
338
+ set_shading on the header cells with the accent color
339
+ set_style_options { headerRow: true }
340
+
341
+ // Cell-text level (superdoc_format, per cell paragraph):
342
+ set_alignment on header (center) and body (left or right)
343
+ inline { color, bold } on header cells
344
+
345
+ // Batch many cell-level format calls via superdoc_mutations format.apply.
346
+ ```
347
+
348
+ Discover the document's palette by reading `superdoc_get_content({action: "blocks"})` and reusing colors from existing tables/headings. When none are obvious, default to `1F3864` (corporate blue) or `444444` (dark grey) for accents and `F2F2F2` / `E7E6E6` for banding. Never use `auto` when a concrete color is implied.
349
+
350
+ **3. After a structural change to a styled table, re-apply the existing styling.**
351
+ Triggers: `insert_row`, `insert_column`, `delete_row`, `delete_column`, `merge_cells`, `unmerge_cells` — but NOT `set_cell_text` or `set_cell` (those don't disturb borders/shading). Read the current borders/shading/cnf flags via `superdoc_get_content({action: "blocks"})` before the change, then re-run the same `set_borders` / `set_shading` / `set_style_options` calls with the SAME values after. Goal is consistency, not redesign. Skip on a freshly created table — a new table starts un-styled.
352
+
353
+ **4. Convert a list to a table.**
354
+ Three steps:
355
+ 1. `superdoc_create({action: "table", rows: N, columns: M, at: ...})`
356
+ 2. Populate cells with `superdoc_table({action: "set_cell_text", ...})` — one call per cell.
357
+ 3. **Delete the source list** with one `superdoc_list` call:
358
+
359
+ ```
360
+ superdoc_list({action: "delete", target: {kind: "block", nodeType: "listItem", nodeId: "<any-item-id>"}})
361
+ ```
362
+
363
+ Wrong paths (all leave bullets/empty paragraphs behind): `text.delete`, `superdoc_edit` action `delete` on text refs, `lists.detach`, `lists.convertToText`. Only `superdoc_list` action `delete` removes the whole list.
364
+
225
365
  ### Add a comment on specific text
226
366
 
227
367
  ```
@@ -278,3 +418,5 @@ When formatting newly created content, use the right source:
278
418
  - **Only pass `dryRun` when the action's schema explicitly lists it.** Do not assume every action accepts it. Prefer a real call over a preview for destructive actions unless dryRun is documented for that action.
279
419
  - **If blocks still report `underline: true` after you explicitly removed it, treat it as a style inheritance artifact.** Do not retry formatting to fix it.
280
420
  - **On "Unknown field" errors, drop the unrecognized field and retry.** Use the narrowest working call shape rather than guessing alternative field names.
421
+ - **Table styling crosses two tools.** Borders / shading / cnf flags / spacing are on `superdoc_table`; cell-text alignment and font color/weight are on `superdoc_format` (paragraph- and run-level). A "style the whole table" pass calls both. See the Tables: cross-tool workflows section for the full recipe.
422
+ - **To delete a list, use `superdoc_list` action `delete`.** Pass any list-item nodeId. Never use `text.delete`, `superdoc_edit` action `delete`, `lists.detach`, or `lists.convertToText` for "remove the list" — they leave empty list-item paragraphs behind.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "policyVersion": "v4",
3
- "toolCount": 9,
3
+ "toolCount": 10,
4
4
  "tools": [
5
5
  {
6
6
  "toolName": "superdoc_get_content",
@@ -37,7 +37,11 @@
37
37
  {
38
38
  "toolName": "superdoc_mutations",
39
39
  "mutates": true
40
+ },
41
+ {
42
+ "toolName": "superdoc_table",
43
+ "mutates": true
40
44
  }
41
45
  ],
42
- "contractHash": "c8670fb494b56c19fbd09a7bada35974fbb3c22d938f6a5e01eee6e8467961c0"
46
+ "contractHash": "91be2034a420f656aa9d85687f48644493955127cae54cfafd5ae70693b33646"
43
47
  }