From 1d9a24e529023f49a7ff9a2e69526d8a507d3bec Mon Sep 17 00:00:00 2001 From: bishnubista Date: Wed, 11 Mar 2026 18:47:58 -0700 Subject: [PATCH 1/3] docs(skills): add COOKBOOK.md for gws-docs with tab and formatting recipes SKILL.md files are auto-generated by `gws generate-skills` and cannot hold hand-written guidance. This adds a COOKBOOK.md alongside gws-docs SKILL.md with practical recipes for: - Tab operations (read, create, write, rename, delete) - Formatted content insertion (headings, bold/italic, styles) - Valid batchUpdate request types reference - `+write` helper limitations and workarounds Closes #420 --- skills/gws-docs/COOKBOOK.md | 179 ++++++++++++++++++++++++++++++++++++ 1 file changed, 179 insertions(+) create mode 100644 skills/gws-docs/COOKBOOK.md diff --git a/skills/gws-docs/COOKBOOK.md b/skills/gws-docs/COOKBOOK.md new file mode 100644 index 00000000..bd0f453d --- /dev/null +++ b/skills/gws-docs/COOKBOOK.md @@ -0,0 +1,179 @@ +# Google Docs Cookbook + +Hand-written recipes for common Google Docs API patterns. These complement the auto-generated [SKILL.md](./SKILL.md). + +## Working with Tabs + +Google Docs supports multiple tabs within a single document. The API requires specific patterns for reading, creating, and writing to tabs. + +### Reading all tabs + +Use `includeTabsContent: true` to get all tabs. Without it, only the first tab's content is returned in the legacy `body` field. + +```bash +gws docs documents get \ + --params '{"documentId": "DOC_ID", "includeTabsContent": true}' +``` + +Response structure: + +```text +doc.tabs[] # Array of top-level tabs + .tabProperties.tabId # Tab identifier (e.g., "t.abc123") + .tabProperties.title # Tab display name + .tabProperties.index # Position (0-based) + .documentTab.body.content[] # Paragraphs, tables, etc. + .childTabs[] # Nested child tabs (same structure) +``` + +> **Note:** Tabs can be nested. When searching for a tab by title, recursively check `childTabs`. + +### Creating a new tab + +Use `addDocumentTab` in a batchUpdate request. + +```bash +gws docs documents batchUpdate \ + --params '{"documentId": "DOC_ID"}' \ + --json '{"requests": [{"addDocumentTab": {"tabProperties": {"title": "My New Tab"}}}]}' +``` + +The response includes the new tab's ID: + +```text +replies[0].addDocumentTab.tabProperties.tabId → "t.xyz789" +``` + +> **Gotcha:** The request type is `addDocumentTab`, not `createTab`. Using `createTab` returns a validation error. + +### Writing content to a specific tab + +Include `tabId` in the `location` object of any content request. + +```bash +gws docs documents batchUpdate \ + --params '{"documentId": "DOC_ID"}' \ + --json '{ + "requests": [{ + "insertText": { + "text": "Hello from a specific tab!\n", + "location": {"index": 1, "tabId": "TAB_ID"} + } + }] + }' +``` + +> **Important:** `tabId` must also be included in `range` objects for formatting requests like `updateParagraphStyle`. + +### Renaming a tab + +```bash +gws docs documents batchUpdate \ + --params '{"documentId": "DOC_ID"}' \ + --json '{ + "requests": [{ + "updateDocumentTabProperties": { + "tabId": "TAB_ID", + "documentTabProperties": {"title": "New Title"}, + "fields": "title" + } + }] + }' +``` + +### Deleting a tab + +```bash +gws docs documents batchUpdate \ + --params '{"documentId": "DOC_ID"}' \ + --json '{"requests": [{"deleteTab": {"tabId": "TAB_ID"}}]}' +``` + +--- + +## Inserting Formatted Content + +The `+write` helper inserts plain text only. For structured content with headings, bold, or styles, use `batchUpdate` with multiple requests. + +### Pattern: insert text then apply styles + +The key principle: insert all text in a **single request**, then apply formatting using character ranges. This avoids index-shifting issues between requests. + +```bash +gws docs documents batchUpdate \ + --params '{"documentId": "DOC_ID"}' \ + --json '{ + "requests": [ + { + "insertText": { + "text": "My Heading\nBody paragraph text.\n", + "location": {"index": 1, "tabId": "TAB_ID"} + } + }, + { + "updateParagraphStyle": { + "paragraphStyle": {"namedStyleType": "HEADING_1"}, + "range": {"startIndex": 1, "endIndex": 11, "tabId": "TAB_ID"}, + "fields": "namedStyleType" + } + } + ] + }' +``` + +### Available paragraph styles + +| Named Style | Usage | +|-------------|-------| +| `TITLE` | Document title | +| `HEADING_1` – `HEADING_6` | Section headings | +| `NORMAL_TEXT` | Body text (default) | + +### Applying bold or italic + +Use `updateTextStyle` with a character range: + +```bash +{ + "updateTextStyle": { + "textStyle": {"bold": true}, + "range": {"startIndex": 1, "endIndex": 11, "tabId": "TAB_ID"}, + "fields": "bold" + } +} +``` + +Replace `"bold": true` with `"italic": true` for italic, or combine both with `"fields": "bold,italic"`. + +### Tips for batch formatting + +- **Insert first, format second.** A single `insertText` followed by multiple `updateParagraphStyle`/`updateTextStyle` requests avoids index math headaches. +- **Track positions manually.** Each character (including `\n`) advances the index by 1. Index 1 is the start of the document body. +- **Atomic batches.** All requests in a single `batchUpdate` are atomic — if any request fails, none are applied. +- **Tab targeting.** When writing to a non-default tab, every `location` and `range` must include the `tabId`. + +--- + +## Valid batchUpdate Request Types + +Quick reference for all supported request types: + +| Category | Requests | +|----------|----------| +| **Tabs** | `addDocumentTab`, `deleteTab`, `updateDocumentTabProperties` | +| **Text** | `insertText`, `deleteContentRange`, `replaceAllText`, `replaceNamedRangeContent` | +| **Formatting** | `updateTextStyle`, `updateParagraphStyle`, `updateDocumentStyle`, `updateSectionStyle` | +| **Lists** | `createParagraphBullets`, `deleteParagraphBullets` | +| **Tables** | `insertTable`, `insertTableRow`, `insertTableColumn`, `deleteTableRow`, `deleteTableColumn`, `mergeTableCells`, `unmergeTableCells`, `updateTableCellStyle`, `updateTableColumnProperties`, `updateTableRowStyle`, `pinTableHeaderRows` | +| **Objects** | `insertInlineImage`, `replaceImage`, `deletePositionedObject`, `insertPerson`, `insertDate` | +| **Structure** | `insertPageBreak`, `insertSectionBreak`, `createHeader`, `deleteHeader`, `createFooter`, `deleteFooter`, `createFootnote` | +| **Named Ranges** | `createNamedRange`, `deleteNamedRange` | + +--- + +## Limitations of `+write` + +The `docs +write` helper has two limitations to be aware of: + +1. **No tab support** — always appends to the first tab. Use `batchUpdate` with `tabId` in `location` to target a specific tab (see [Writing content to a specific tab](#writing-content-to-a-specific-tab)). +2. **Plain text only** — no formatting. Use `batchUpdate` with `updateParagraphStyle`/`updateTextStyle` for structured content (see [Inserting Formatted Content](#inserting-formatted-content)). From 827adac7bb41ae35ede155e2b821a981a7ca3a5a Mon Sep 17 00:00:00 2001 From: bishnubista Date: Wed, 11 Mar 2026 19:02:45 -0700 Subject: [PATCH 2/3] fix(docs): address Gemini review comments on COOKBOOK.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix off-by-one: updateParagraphStyle endIndex 11→12 (exclusive, must include the trailing newline of "My Heading\n") - Make bold/italic example a full runnable gws command instead of a bare JSON snippet in a bash code block --- skills/gws-docs/COOKBOOK.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/skills/gws-docs/COOKBOOK.md b/skills/gws-docs/COOKBOOK.md index bd0f453d..e5307a0b 100644 --- a/skills/gws-docs/COOKBOOK.md +++ b/skills/gws-docs/COOKBOOK.md @@ -113,7 +113,7 @@ gws docs documents batchUpdate \ { "updateParagraphStyle": { "paragraphStyle": {"namedStyleType": "HEADING_1"}, - "range": {"startIndex": 1, "endIndex": 11, "tabId": "TAB_ID"}, + "range": {"startIndex": 1, "endIndex": 12, "tabId": "TAB_ID"}, "fields": "namedStyleType" } } @@ -131,16 +131,20 @@ gws docs documents batchUpdate \ ### Applying bold or italic -Use `updateTextStyle` with a character range: +Use `updateTextStyle` with a character range to apply styles like bold or italic. ```bash -{ - "updateTextStyle": { - "textStyle": {"bold": true}, - "range": {"startIndex": 1, "endIndex": 11, "tabId": "TAB_ID"}, - "fields": "bold" - } -} +gws docs documents batchUpdate \ + --params '{"documentId": "DOC_ID"}' \ + --json '{ + "requests": [{ + "updateTextStyle": { + "textStyle": {"bold": true}, + "range": {"startIndex": 1, "endIndex": 11, "tabId": "TAB_ID"}, + "fields": "bold" + } + }] + }' ``` Replace `"bold": true` with `"italic": true` for italic, or combine both with `"fields": "bold,italic"`. From e2f2c5172bc651a67a714604f4d60e85318dbf9f Mon Sep 17 00:00:00 2001 From: bishnubista Date: Wed, 11 Mar 2026 19:09:27 -0700 Subject: [PATCH 3/3] fix(docs): clarify combined bold+italic requires both textStyle and fields Address Gemini review: the previous wording only mentioned updating the fields mask but omitted that the textStyle object must also contain both properties for the API call to work correctly. --- skills/gws-docs/COOKBOOK.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skills/gws-docs/COOKBOOK.md b/skills/gws-docs/COOKBOOK.md index e5307a0b..b36bc77f 100644 --- a/skills/gws-docs/COOKBOOK.md +++ b/skills/gws-docs/COOKBOOK.md @@ -147,7 +147,7 @@ gws docs documents batchUpdate \ }' ``` -Replace `"bold": true` with `"italic": true` for italic, or combine both with `"fields": "bold,italic"`. +Replace `"bold": true` with `"italic": true` for italic, or combine both by setting `"textStyle": {"bold": true, "italic": true}` and `"fields": "bold,italic"`. ### Tips for batch formatting