# rust-web-docx: full agent guides # @dimarborda/docx-editor — guide for AI coding agents Read this before writing code that uses this package. It is the short, complete map of the library; the exact signatures and JSDoc are in `src/index.d.ts` next to this file. ## What it is A Word (`.docx`) editor for the browser. A Rust + WebAssembly engine reads the file, lays out the pages like Word and draws them on a ``; edits are written back losslessly (what is not touched stays as it was). Framework-agnostic: a class (`DocxEditor`) and two custom elements (``, ``). No server, no network calls of its own. - **Browser only.** There is no headless/Node API for `.docx` (unlike `@dimarborda/xlsx-editor`, which has `/engine`). Importing is SSR-safe; creating the editor needs the DOM. - The editor's container **must have a height** (`height: 80vh`, or a sized flex/grid child). - In production with a CSP, `script-src` needs `'wasm-unsafe-eval'`. ## Setup ```js import { DocxEditor } from '@dimarborda/docx-editor'; import '@dimarborda/docx-editor/style.css'; import '@dimarborda/docx-editor/fonts'; // optional: metric-compatible Calibri, Cambria, Arial… const editor = await DocxEditor.create(container, { locale: 'en' }); // locale: 'es' (default) | 'en' await editor.open(fileOrBytes); // File, Blob, ArrayBuffer or Uint8Array // or: editor.openBlank({ pageSize: 'a4' | 'letter' | 'legal' }) const docx = editor.save(); // Uint8Array of the edited .docx const pdf = await editor.exportPdf(); // Uint8Array, vector PDF (pdf-lib loads lazily) editor.destroy(); // removes editor.root, leaves the container alone ``` Web component alternative: ```html ``` React/Next.js: create it in `useEffect`, and in StrictMode destroy the instance that resolves after cleanup (`if (disposed) return e.destroy()`). If the bundler does not serve the `.wasm`, pass `{ wasmUrl }` (file: `node_modules/@dimarborda/docx-editor/wasm/docx_engine_bg.wasm`). ## Mental model (get these right) - **Positions are paragraph indexes** in body order, from 0, as in `structure()` and `selection`. Table cells count as paragraphs (`tables()[i].first/last`). Text boxes use indexes ≥ 2²⁴ and headers/footers ≥ 2²⁵ — never compute those by hand. - **Indexes shift** after inserting or deleting. Re-read `structure()`, `tables()` or `markers()` after each structural change instead of reusing old numbers. Exceptions: `fillMarker` relocates its marker, and activity marks and section keys follow their content. - **Prefer stable handles over indexes:** section keys (`sections()`), markers (`markers()`), table header text (`tables().find(t => t.header[0] === 'Risk')`). - **Every API edit is one undo step**, fires `change`, flashes the changed area for the user and does not move the user's caret or scroll. API edits also work in read-only mode and inside locked sections (locks restrict the person, not code). - **Errors are atomic:** invalid calls throw an `Error` with a clear message and leave the document untouched. Catch, re-read state, and retry differently. ## Reading a document | Call | Returns | | :--- | :--- | | `text()` | Plain text of the whole document | | `structure()` | Body blocks in order: `{ kind: 'paragraph', paragraph, text, headingLevel }` and `{ kind: 'table', index, first, last, rows, cols }` | | `markers({ kind, section })` | Fields and pending items (see below), with `paragraph`, `offset`, `length`, `place`, `section` | | `variables()` | Field texts exactly as written, e.g. `['{{CLIENT}}', '{DATE}']` | | `sections()` / `sectionContent(key)` | Logical sections and their text | | `tables()` | `{ index, rows, cols, header, first, last }` per body table | | `images()` | Images, text boxes and shapes with size, wrap and position | | `stats()` / `fonts()` / `paragraphStyles()` | Counts, fonts used, available paragraph styles | ## Markers: what is left to fill in Standard lexicon shared with `@dimarborda/xlsx-editor`: | Written as | `kind` | Meaning | | :--- | :--- | :--- | | `{{KEY}}`, `{{ KEY }}`, `{{KEY: hint}}`, `{key}`, `{key:hint}` | `field` | A value an automation fills in | | `[…]`, `[...]`, `[____]`, `[Complete: project scope]` | `pending` | Prose someone still has to write; the inner text is the hint | Not markers: citations (`[1]`, `[2, 3]`), checkboxes (`[ ]`, `[x]`), `[sic]`. No marker spans a line break. ```js editor.replaceVariables({ CLIENT: 'Acme Inc.', DATE: 'October 7, 2026' }); // key without braces fills every form for (const m of editor.markers({ kind: 'pending' })) { editor.fillMarker(m, await draft(m.hint, m.section)); // keeps the marker's formatting } editor.nextMarker({ kind: 'pending', wrap: true }); // first marker after the selection, or null ``` `findMarkers(text)` (exported) checks a loose string — e.g. text you are about to insert — for leftover markers. ## Writing content ```js editor.insertParagraphs([ { text: 'Scope', style: 'Heading1' }, // 'Title' | 'Heading1' | 'Heading2' | 'Heading3' { text: 'Important', bold: true, align: 'center', fontSize: 14 }, 'A plain paragraph.', ], { at: 'end' }); // 'cursor' (default) | 'start' | 'end' | { paragraph, offset } editor.insertText('inline text', { at: { paragraph: 4, offset: 10 } }); editor.replaceParagraphs({ from: 3, to: 7 }, ['New text']); // whole blocks, tables in between included editor.deleteParagraphs({ from: 3, to: 7 }); editor.moveParagraphs({ from: 3, to: 7 }, 12); // keeps formatting, images, links editor.findReplace('Bogota', 'Bogotá', { matchCase: true }); ``` Ranges fail if they cut through a table or include a Word section break. ## Logical sections (work section by section) Sections are block content controls in the `.docx` (`w:sdt`, `w:tag="section:"`), so they survive Word round-trips. ```js editor.sectionsFromHeadings({ fromLevel: 1, toLevel: 2 }); // create from Heading styles; idempotent editor.defineSection('introduction', { from: 2, to: 6 }, { title: 'Introduction' }); editor.sectionContent('introduction').text; editor.replaceSection('introduction', ['New paragraph.'], { keepHeading: true }); editor.insertSection('annexes', { title: 'Annexes', after: 'conclusions' }); editor.setSectionLock('legal', { contentLocked: true }); // user cannot edit; code still can editor.setSectionSchema({ type: 'report', sections: [{ key: 'introduction', title: 'Introduction' }] }); editor.validateSections(); // { valid, issues: [{ kind: 'missing' | 'empty' | 'order' | … }] } editor.addMissingSections(); ``` ## Tables ```js editor.insertTable({ rows: [['Item', 'Amount'], ['Design', '$1,200']], widths: [2, 1], align: ['left', 'right'] }, { at: 'end' }); const t = editor.tables().find(t => t.header[0] === 'Risk'); // existing template table const { first } = editor.insertTableRows(t.index, t.rows, [['Outage', 'Medium']], { copyFormatFrom: 1 }); editor.setTableCell(t.index, first, 1, 'High'); // keeps the cell's text format editor.deleteTableRows(t.index, first, 1); editor.duplicateTable(t.index, { at: 'end', values: [[null, 'New value']] }); // null keeps text ``` - Rows, columns and tables are 0-based; for a merged cell write to its first grid column. - When inserting right under a header row, pass `copyFormatFrom` with a **data** row, otherwise the header shading is copied. - To reproduce a styled template table (record card, signature block) use `duplicateTable`, not `insertTable` (which uses the editor's own table style). Nested tables are not supported. ## Images ```js await editor.insertImage(bytes, { width: 200, align: 'center', alt: 'Logo' }); // PNG, JPEG, GIF; ≤ 15 MB const [logo] = editor.images(); // ref = { paragraph, index } editor.updateImage(logo, { wrap: 'square', horizontal: { relativeTo: 'margin', align: 'right' } }); ``` Sizes are CSS px at 100 % zoom. `wrap`: `inline` (default) | `square` | `tight` | `through` | `topAndBottom` | `behind` | `inFront`. ## Showing the user what an agent does ```js const work = editor.markActivity('conclusions', { label: 'Agent · drafting' }); // section key, range or span const added = editor.replaceSection('conclusions', ['First draft…']); work.update(added, { label: 'Agent · reviewing' }); work.done(); ``` View-only: never saved or printed. Use `color` to distinguish several agents and `ttl` to expire. `setHighlightPending({ fields: true })` highlights what is still pending. ## Events `load`, `change`, `selectionchange` (`detail.format`), `imageselect`, `readonlychange`, `message` (user-facing notices), `activity`. Save on `change`. ## Do and don't - Do read the state (`structure()`, `markers()`, `sections()`) before editing, and again after. - Do fill templates with `replaceVariables` / `fillMarker` rather than `findReplace` on braces: they keep the original formatting and handle every field syntax. - Don't build `.docx` XML yourself or unzip the file to edit it: use the API, which keeps the document lossless and undoable. - Don't assume Word section breaks, comments, tracked changes, multiple columns or grouped shapes are editable: they are preserved but not drawn yet. Tables and images cannot go inside headers, footers or text boxes. - Don't send the document anywhere from library code; if your app sends content to a model, that is the app's decision. ## More Context7 library ID: `/dimarborda/rust-web-docx` (both packages; use the ID, other libraries share the `docx-editor` name). Full docs: https://github.com/dimarborda/rust-web-docx/tree/master/docs/docx-editor/en (API, guides, integrations, security). Demo: https://rust-web-docx.dimarborda.workers.dev --- # @dimarborda/xlsx-editor — guide for AI coding agents Read this before writing code that uses this package. It is the short, complete map of the library; exact signatures and JSDoc are in `src/engine.d.ts` (workbook API) and `src/index.d.ts` (browser editor) next to this file. ## What it is A Rust + WebAssembly engine for Excel (`.xlsx`) workbooks, with two entry points: | Import | Where | What | | :--- | :--- | :--- | | `@dimarborda/xlsx-editor/engine` | Node.js ≥ 20 and browser | Headless `Workbook` / `Sheet` API: read, write, recalculate, format, restructure | | `@dimarborda/xlsx-editor` | Browser | `` element and `XlsxEditor` class (canvas grid), plus everything from `/engine` | Saving is lossless: only the parts and cells that changed are rewritten; charts, images, conditional formats, validations and the rest stay byte for byte. No server, no network calls. ## Headless engine (scripts, servers, agent tools) ```js import { readFile, writeFile } from 'node:fs/promises'; import { openWorkbook, createWorkbook } from '@dimarborda/xlsx-editor/engine'; const wb = await openWorkbook(await readFile('template.xlsx')); // or await createWorkbook() const sheet = wb.sheet('Sales'); // name (ignores case) or 0-based tab index sheet.getValue('B10'); // formula result, already recalculated sheet.getCell('D2'); // { ref, type, value, formula? } sheet.getValues('A1:C3'); // matrix; empty cells are null sheet.setValue('B3', 42); // text, number, boolean, Date, null (clears value, keeps format) sheet.setValue('D2', { formula: '=B2*C2' }); // formulas: English syntax, leading '=' sheet.setValues('A2', [['Keyboard', 3, 29.9], ['Mouse', 5, 12.5]]); // undefined leaves a cell as is wb.fillTemplate({ CLIENT: 'Acme Inc.' }); // {{CLIENT}}, {{ CLIENT }}, {{CLIENT: hint}} await writeFile('output.xlsx', wb.toBytes()); wb.close(); // frees WASM memory; the workbook is unusable afterwards ``` In Node the engine loads by itself. In the browser bundlers serve the `.wasm`; otherwise call `initEngine(url)` first. In production with a CSP, `script-src` needs `'wasm-unsafe-eval'`. ## Understand a sheet without reading all of it Do not dump whole sheets into a prompt. Ask for areas, then the context of the one you need: ```js wb.areas(); // [{ kind, name, sheetName, ref, address, title, columns, … }] const ctx = wb.area('Sales'); // table / defined name, 'Sheet!A1:H40', or a range with { sheet } ctx.columnsInfo; // per column: name, type, filled, numbers { sum, min, max, average }, exampleFormula ctx.markdown; // header + first rows (maxRows, default 30) as a Markdown table ctx.truncated; ctx.rowCount; sheet.regionAt('B5'); // the block Ctrl+A would select around a cell ``` `kind` is `table` (Excel tables), `name` (defined names), `printArea`, `filter` or `region` (adjacent filled cells, with detected title and header). `address` works as a target anywhere. ## Addresses and numbering - Cells and ranges are A1 strings: `'B3'`, `'A1:C4'`, `'B:D'`, `'3:5'`. - **Rows and column numbers are 1-based, as in Excel** (`insertRows(2)` inserts before row 2; column `2` is `B`). Columns also accept letters. **Sheet indexes are 0-based.** - Move/insert/delete use numbering *as it is now*; afterwards re-read areas or cells rather than reusing old references. ## Formulas - Written in **English syntax** (`SUM`, `IF`, `,` between arguments; `;` accepted). Spanish names like `SUMA` are not translated. - About 90 functions are evaluated (math, conditional `*IFS`, logic, `VLOOKUP`/`XLOOKUP`/`INDEX`/ `MATCH`, text, dates). Anything else (`OFFSET`, `INDIRECT`, external links, structured table references, array/spill formulas, circular references) keeps the value Excel saved, and Excel recalculates on open (`fullCalcOnLoad` is set). So `getValue` may return a stale value for those. - Every write recalculates dependents; `wb.recalculate()` refreshes a freshly opened workbook. ## Dates Excel dates have no time zone: they map to the **UTC** fields of a `Date`. Write `new Date(Date.UTC(2024, 0, 15))`, read with `getUTCFullYear()` / `toISOString()`. Writing a `Date` gives the cell a date format if it had none. ## Format and structure ```js sheet.setFormat('A1:D1', { bold: true, fill: '#DDEBF7', border: { style: 'thin', edges: 'outline' } }); sheet.setFormat('D2:D50', { numberFormat: '#,##0.00', align: 'right' }); sheet.setColumnWidth('B:D', 14); // characters sheet.setRowHeight('3:5', 30); // points; null = automatic sheet.mergeCells('A1:H1'); sheet.insertRows(2, 3); sheet.deleteRows(10, 2); sheet.moveRows(4, 2, 9); sheet.insertColumns('C'); sheet.moveColumns('B', 1, 'E'); sheet.fill('D2', 'D2:D20'); // fill handle: formulas shift, series continue sheet.setRowsHidden('3:5', true); wb.addSheet('Notes'); wb.renameSheet('Sheet1', 'Data'); // rename updates references ``` `setFormat` changes only the given properties. Structural changes update formulas, names, charts, tables, validations and conditional formats across the whole workbook, like Excel. **Refusals are safe:** an operation that would tear merged cells, a multi-cell array formula, a table or a filter, or touch a sheet with pivot tables, **throws and changes nothing**. Catch the error and tell the user why (engine messages are in Spanish). ## Markers Same lexicon as `@dimarborda/docx-editor`: `{{KEY}}` / `{key}` are `field`s an automation fills; `[…]`, `[____]`, `[Complete: scope]` are `pending` content to write. ```js for (const m of wb.markers({ kind: 'pending' })) { // m: { sheet, sheetName, ref, row, col, raw, key, hint, offset, length } const s = wb.sheet(m.sheet); // m.sheet is the 0-based tab index const text = String(s.getValue(m.ref)); s.setValue(m.ref, text.replace(m.raw, await draft(m.hint))); // the marker may be only part of the cell } ``` For `{{FIELD}}`s, `wb.fillTemplate(values)` is simpler and keeps the rest of each text. ## Undo history (engine) `wb.checkpoint(selection?)` opens an undo step; edits until the next checkpoint undo together. `wb.undo()` / `wb.redo()` return `{ selection, parts }` or `null`. ## Browser editor ```html ``` ```js el.addEventListener('ready', async ({ detail: { editor } }) => { await editor.open(file); // URL, File/Blob, ArrayBuffer, Uint8Array editor.setSheet('Summary'); editor.select('B3:D7'); // Changes through the workbook API: checkpoint first (undo step), reload after (redraw) editor.workbook.checkpoint(); editor.workbook.sheet('Summary').setValues('B2', [[1200, 0.25]]); editor.reload(); editor.moveRows(4, 2, 9); // editor methods: one undo step each, false if refused editor.selectionContext(); // AreaContext of what the user selected editor.download('report.xlsx'); // or editor.toBytes() }); el.addEventListener('change', ({ detail }) => save(detail)); // { sheet, range, source, cells } ``` Without the element: `const editor = await XlsxEditor.create(container, { locale: 'en' })`. The container needs a height. Events: `ready`, `load`, `sheetchange`, `selectionchange`, `change`, `error`. Editor methods return `false` on refusal and emit `error` with the reason. ## Do and don't - Do use `areas()` / `area()` to get structure, types and totals instead of reading every cell. - Do write numbers as numbers and dates as `Date` — not preformatted strings — so formulas and formats keep working. `parseInput(text, locale)` converts user-typed text the way the grid does. - Do call `editor.reload()` after changing `editor.workbook` from code, and `checkpoint()` before. - Don't unzip or rewrite the `.xlsx` XML yourself: the engine keeps it lossless. - Don't expect charts, images, conditional formats or comments to be drawn in the grid: they are preserved in the file but not rendered yet. - A `{{FIELD}}` split across differently formatted runs inside one cell is not detected. ## More Context7 library ID: `/dimarborda/rust-web-docx` (both packages; use the ID, other libraries share the `docx-editor` name). Full docs: https://github.com/dimarborda/rust-web-docx/tree/master/docs/xlsx-editor/en (component, operations, engine, agents).