Skip to content

docs.getText (and other tab methods) do not recurse into nested/child tabs #207

@Andre0991

Description

@Andre0991

Problem

PR #60 added tab support to DocsService by using includeTabsContent: true and iterating over res.data.tabs. Issue #138 reported that tabs were not being returned, and was closed referencing PR #60 as the fix.

However, the Google Docs API returns tabs as a tree structure — each tab can have childTabs. The current code only iterates the top-level array (res.data.tabs), so nested (child) tabs are silently ignored.

Methods getText, appendText, and replaceText all assign:

const tabs = res.data.tabs || [];

Then use tabs.find(...) / tabs.map(...) on that flat top-level array, never reaching any tab nested under childTabs.

Suggested fix (not a formal PR)

I quickly asked the AI to fix this, but I don't know how this works and all the edge cases, so I'm sharing it here as an example rather than opening a PR. The idea is to recursively flatten the tab tree before using it:

diff --git a/workspace-server/src/services/DocsService.ts b/workspace-server/src/services/DocsService.ts
index b62b7df..4ed5957 100644
--- a/workspace-server/src/services/DocsService.ts
+++ b/workspace-server/src/services/DocsService.ts
@@ -24,6 +24,21 @@ import {
 export class DocsService {
   private purify: ReturnType<typeof createDOMPurify>;

+  /**
+   * Recursively flattens a tab tree into a single array,
+   * so that nested (child) tabs are included alongside top-level ones.
+   */
+  private _flattenTabs(tabs: docs_v1.Schema$Tab[]): docs_v1.Schema$Tab[] {
+    const result: docs_v1.Schema$Tab[] = [];
+    for (const tab of tabs) {
+      result.push(tab);
+      if (tab.childTabs && tab.childTabs.length > 0) {
+        result.push(...this._flattenTabs(tab.childTabs));
+      }
+    }
+    return result;
+  }
+
   constructor(
     private authManager: AuthManager,
     private driveService: DriveService,
@@ -327,7 +342,7 @@ export class DocsService {
         includeTabsContent: true,
       });

-      const tabs = res.data.tabs || [];
+      const tabs = this._flattenTabs(res.data.tabs || []);

       // If tabId is provided, try to find it
       if (tabId) {
@@ -476,7 +491,7 @@ export class DocsService {
         includeTabsContent: true,
       });

-      const tabs = res.data.tabs || [];
+      const tabs = this._flattenTabs(res.data.tabs || []);
       let content: docs_v1.Schema$StructuralElement[] | undefined;

       if (tabId) {
@@ -585,7 +600,7 @@ export class DocsService {
         includeTabsContent: true,
       });

-      const tabs = docBefore.data.tabs || [];
+      const tabs = this._flattenTabs(docBefore.data.tabs || []);

       const requests: docs_v1.Schema$Request[] = [];

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions