Skip to content

Explore/gdocs#357

Open
monicaz010101 wants to merge 6 commits intomainfrom
explore/gdocs
Open

Explore/gdocs#357
monicaz010101 wants to merge 6 commits intomainfrom
explore/gdocs

Conversation

@monicaz010101
Copy link
Contributor

No description provided.

kcarnold and others added 5 commits January 30, 2026 15:48
Copilot / Claude Opus 4.5

It said: Next Steps
Install clasp: npm install -g @google/clasp
Login: clasp login
Create project: cd google-docs-addon && clasp create --type docs --title "Writing Tools"
Push code: clasp push
Test: Open Apps Script editor → Deploy → Test deployments → Install
Configure backend URL in Script Properties
The React app will need a webpack config for bundling into the sidebar, but the Apps Script functions can be tested immediately using the test button in sidebar.html.
Copilot AI review requested due to automatic review settings February 24, 2026 21:17
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an initial Google Docs add-on implementation (“Writing Tools”) and supporting frontend build entrypoints to run the existing React UI inside a Docs sidebar.

Changes:

  • Introduces an Apps Script add-on (Code.gs, sidebar.html, manifest, clasp config/ignore) to show a sidebar and perform document operations + backend proxying.
  • Adds a Google Docs-specific webpack config and React entrypoint (index-gdocs.tsx) plus an editor API adapter with selection polling.
  • Updates frontend API base URL/export surface and adds build/dev scripts + gitignore entries for new build outputs.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
google-docs-addon/sidebar.html Sidebar bootstrap + Apps Script bridge + dev/prod loading strategy
google-docs-addon/appsscript.json Apps Script manifest stub
google-docs-addon/README.md Add-on architecture + setup/deployment docs
google-docs-addon/Code.gs Apps Script menu/sidebar entrypoints, doc ops, backend proxy, user properties
google-docs-addon/.claspignore Restricts clasp pushes to add-on files
google-docs-addon/.clasp.json Clasp project linkage config
frontend/webpack.google-docs.config.js Webpack build/dev-server for Docs bundle
frontend/src/index-gdocs.tsx React entrypoint for Docs sidebar runtime
frontend/src/api/index.ts API constants and platform/API exports
frontend/src/api/googleDocsEditorAPI.ts Docs EditorAPI implementation + selection polling
frontend/package.json Adds Docs build + dev-server scripts
.gitignore Ignores new build output directories

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

allowedHosts: 'all',
headers: {
'Access-Control-Allow-Origin': '*'
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The devServer here is HTTP-only (port: 3001). When loaded inside the Google Docs sidebar (served over HTTPS), browsers commonly block HTTP subresources as mixed content. Consider enabling HTTPS for this dev server (similar to frontend/webpack.config.js) so the dev workflow works without insecure-content exceptions.

Suggested change
}
},
https: true

Copilot uses AI. Check for mistakes.
Comment on lines +181 to +186
function selectPhrase(phrase) {
const doc = DocumentApp.getActiveDocument();
const body = doc.getBody();

const searchResult = body.findText(phrase);

Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

selectPhrase passes phrase directly into body.findText(...), which treats the input as a regular expression. If the phrase includes regex metacharacters (e.g., ., ?, (), the search can behave incorrectly or error. Consider escaping the phrase to a literal regex before calling findText.

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +124
## Key Differences from Word Add-in

| Aspect | Word Add-in | Google Docs Add-on |
|--------|-------------|-------------------|
| Document API | Office.js (client-side) | DocumentApp (server-side via Apps Script) |
| UI Framework | HTML/JS in taskpane | HTML/JS in sidebar |
| API calls | Direct from browser | Proxied through Apps Script |
| Selection events | Native Office events | Polling (no native events) |
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README documents specific OAuth scopes in appsscript.json, but the manifest in this PR doesn’t declare oauthScopes at all. This can lead to unexpected authorization prompts/behavior and makes Marketplace deployment harder. Either add the documented scopes to the manifest or adjust the README section to match the actual manifest strategy.

Copilot uses AI. Check for mistakes.
Comment on lines +87 to +90
pollingInterval = setInterval(() => {
void pollForChanges();
}, 1000); // Poll every second
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Selection polling calls getDocContext() via Apps Script every 1s while any handler is registered. This can consume Apps Script quotas quickly (and adds latency/battery usage). Consider making the interval configurable, pausing when the sidebar is hidden/inactive, and/or using a backoff when repeated polling errors occur.

Copilot uses AI. Check for mistakes.
Comment on lines +52 to +67
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css'
}),
new webpack.DefinePlugin({
'process.env.AUTH0_DOMAIN': JSON.stringify('dev-rbroo1fvav24wamu.us.auth0.com'),
'process.env.AUTH0_CLIENT_ID': JSON.stringify('YZhokQZRgE2YUqU5Is9LcaMiCzujoaVr'),
'process.env.NODE_ENV': JSON.stringify(options.mode || 'development')
}),
// Generate a standalone HTML file for testing
new HtmlWebpackPlugin({
filename: 'sidebar-bundled.html',
template: path.resolve(__dirname, '../google-docs-addon/sidebar.html'),
inject: 'body',
scriptLoading: 'blocking'
})
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This config uses MiniCssExtractPlugin, which will emit a separate google-docs.css file. Apps Script sidebars generally can't serve arbitrary static CSS/JS files, and the comments/docs indicate the build should inline everything into a single HTML file. Consider switching to an inlining approach (inline CSS + JS into the generated sidebar-bundled.html) or adjusting the Apps Script project structure to host the emitted assets reliably.

Copilot uses AI. Check for mistakes.
"timeZone": "America/New_York",
"dependencies": {
},
"exceptionLogging": "STACKDRIVER",
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This manifest is missing key fields implied by the code/README (e.g., oauthScopes, add-on configuration/entry points, and any required triggers). As written, the script will rely on automatic scopes and may not behave correctly for Workspace Marketplace deployment, and onHomepage/onFileScopeGranted won't be wired without addOns manifest entries.

Suggested change
"exceptionLogging": "STACKDRIVER",
"exceptionLogging": "STACKDRIVER",
"oauthScopes": [
"https://www.googleapis.com/auth/documents.currentonly",
"https://www.googleapis.com/auth/script.container.ui"
],
"addOns": {
"common": {
"name": "Google Docs Add-on",
"logoUrl": "https://www.example.com/logo.png",
"layoutProperties": {
"primaryColor": "#4285F4",
"secondaryColor": "#FFFFFF"
},
"useLocaleFromApp": true
},
"docs": {
"homepageTrigger": {
"runFunction": "onHomepage",
"enabled": true
},
"onFileScopeGrantedTrigger": {
"runFunction": "onFileScopeGranted"
}
}
},

Copilot uses AI. Check for mistakes.
Comment on lines +11 to +25
│ │ HTML/JS Sidebar (React app from frontend/) │ │
│ │ │ │
│ │ google.script.run.getDocContext() ──────────┐ │ │
│ │ google.script.run.analyzeText(...) │ │ │
│ └────────────────────────────────────────────────│────┘ │
└───────────────────────────────────────────────────│─────────┘
┌─────────────────────────────────────────────────────────────┐
│ Apps Script (Code.gs) │
│ │
│ • getDocContext() → reads doc, returns to sidebar │
│ • selectPhrase() → finds and selects text in doc │
│ • proxyToBackend() → UrlFetchApp to Python backend │
└─────────────────────────────────────────────────────────────┘
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README states that API calls are “Proxied through Apps Script”, but the current React app still calls the backend directly via SERVER_URL fetches. Either update the README to reflect the current behavior, or update the frontend to route backend calls through the Apps Script bridge so the architecture diagram matches reality.

Copilot uses AI. Check for mistakes.
@@ -1,4 +1,28 @@
export const SERVER_URL = '/api';
export const SERVER_URL = 'https://app.thoughtful-ai.com/api';
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SERVER_URL was changed from a relative /api (which works with the webpack devServer proxy in frontend/webpack.config.js) to an absolute production URL. This will break local/dev builds and any deployment where the frontend and backend are co-hosted behind the same origin. Suggest making this environment-driven (e.g., default to /api and override via a build-time env var) so dev proxying continues to work.

Suggested change
export const SERVER_URL = 'https://app.thoughtful-ai.com/api';
export const SERVER_URL =
(typeof process !== 'undefined' &&
process.env &&
process.env.SERVER_URL) ||
'/api';

Copilot uses AI. Check for mistakes.
Comment on lines +123 to +125
const text = element.getElement().asText();
if (text) {
if (element.isPartial()) {
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDocContext calls element.getElement().asText() for every RangeElement. asText() will throw for non-Text element types (e.g., paragraph/list/table elements, images), which can break the sidebar on common selections. Consider checking getType() and using editAsText() where appropriate, or skipping non-text elements safely.

Suggested change
const text = element.getElement().asText();
if (text) {
if (element.isPartial()) {
const elem = element.getElement();
const elemType = elem.getType();
// Only call asText() on TEXT elements; for other element types, try editAsText()
let text = null;
if (elemType === DocumentApp.ElementType.TEXT) {
text = elem.asText();
} else if (typeof elem.editAsText === 'function') {
try {
text = elem.editAsText();
} catch (e) {
// Element cannot be edited as text; skip it safely.
text = null;
}
}
if (text) {
if (element.isPartial() && elemType === DocumentApp.ElementType.TEXT) {

Copilot uses AI. Check for mistakes.
Comment on lines +137 to +142
// Find the selection in the full text to determine before/after
const selectionStart = fullText.indexOf(selectedText);
if (selectionStart !== -1) {
beforeCursor = fullText.substring(0, selectionStart);
afterCursor = fullText.substring(selectionStart + selectedText.length);
}
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getDocContext derives before/after by doing fullText.indexOf(selectedText) (and similarly indexOf(textContent) for the cursor). This is unreliable when the selected text appears multiple times, and it can fail entirely for multi-element selections because selectedParts.join('') drops paragraph separators that exist in fullText. A more robust approach should compute offsets based on the selection/cursor element position rather than substring searching.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants