Skip to content

feat: dynamic column mapping for Run AI#22

Merged
aaronbrezel merged 12 commits into
developfrom
feature/dynamic-column-mapping
Feb 24, 2026
Merged

feat: dynamic column mapping for Run AI#22
aaronbrezel merged 12 commits into
developfrom
feature/dynamic-column-mapping

Conversation

@aaronbrezel

@aaronbrezel aaronbrezel commented Feb 24, 2026

Copy link
Copy Markdown
Member

Summary

  • Replaces hardcoded column names (source_drive, system_prompt, etc.) and AIMode with a RunConfig interface where users select column mappings at runtime via the sidebar
  • Rewrites runBatchAI to accept RunConfig — validates all selected columns by name, auto-creates output column if missing, supports explicit row range or falls back to active sheet selection
  • Replaces the TEXT/FILE modal dialog with an inline sidebar config panel: reads sheet headers on open, presents checkbox lists for user prompt + Drive file columns, dropdowns for system prompt + output column, and a row range toggle

Changes

  • src/shared/types.ts — removes AIMode, ColumnMap, ColumnConfig; adds RunConfig
  • src/server/config.ts — removes COLUMNS from CONFIG
  • src/server/inference.tsdriveLinks and systemPrompt are now optional params with explicit !== undefined guards
  • src/server/utils.ts — adds resolveColumns(headers, names): number[] pure helper
  • src/server/index.ts — rewrites runBatchAI(config: RunConfig), adds getSheetHeaders(), removes showSourceDialog/handleDialogSelection
  • src/server/dialog.ts — deleted
  • rollup.config.js — footer stubs updated to match new exports
  • src/Sidebar.html — inline AI config panel replaces old modal trigger

Test Plan

  • 91 tests passing (npm test)
  • Coverage thresholds met (npm run test:coverage)
  • Zero typecheck errors (npm run typecheck)
  • Zero lint errors (npm run lint)
  • Clean build (npm run build)
  • Manual: open sidebar → Run AI Inference → verify headers populate
  • Manual: select user prompt columns, output column → run on selected rows → verify output written
  • Manual: select Drive file columns → verify multimodal path works
  • Manual: "New column..." path → verify new column created with ai_ prefix

🤖 Generated with Claude Code

aaronbrezel and others added 12 commits February 24, 2026 10:51
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce params

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d getSheetHeaders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ccess

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@aaronbrezel aaronbrezel merged commit 9844cd8 into develop Feb 24, 2026
1 check passed
aaronbrezel added a commit that referenced this pull request Mar 10, 2026
* docs: add sidebar feature parity design doc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add sidebar feature parity implementation plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* build: copy Sidebar.html to dist/ alongside appsscript.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add Sidebar.html for persistent sidebar UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* build: update rollup footer stubs for showSidebar and runTool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: update menu tests for sidebar UX (failing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace menu with sidebar — add showSidebar and runTool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restore importDriveLinks, fix test mocks for runTool dispatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add beforeEach clearAllMocks to runTool describe block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: pass event explicitly in Sidebar run() to avoid window.event global

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: allow .html files through .claspignore so Sidebar.html is pushed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace menu with persistent sidebar (feature parity) (#12) (#13)

* docs: add sidebar feature parity design doc



* docs: add sidebar feature parity implementation plan



* build: copy Sidebar.html to dist/ alongside appsscript.json



* feat: add Sidebar.html for persistent sidebar UI



* build: update rollup footer stubs for showSidebar and runTool



* test: update menu tests for sidebar UX (failing)



* feat: replace menu with sidebar — add showSidebar and runTool



* fix: restore importDriveLinks, fix test mocks for runTool dispatch



* test: add beforeEach clearAllMocks to runTool describe block



* fix: pass event explicitly in Sidebar run() to avoid window.event global



* fix: allow .html files through .claspignore so Sidebar.html is pushed



---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Dev default branch (#14)

* feat: replace menu with persistent sidebar (feature parity) (#12)

* docs: add sidebar feature parity design doc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add sidebar feature parity implementation plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* build: copy Sidebar.html to dist/ alongside appsscript.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add Sidebar.html for persistent sidebar UI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* build: update rollup footer stubs for showSidebar and runTool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: update menu tests for sidebar UX (failing)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace menu with sidebar — add showSidebar and runTool

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restore importDriveLinks, fix test mocks for runTool dispatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add beforeEach clearAllMocks to runTool describe block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: pass event explicitly in Sidebar run() to avoid window.event global

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: allow .html files through .claspignore so Sidebar.html is pushed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* setting app script ids and develop hooks

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add GitHub Actions clasp deployment design

Covers WIF + --adc as primary approach and custom OAuth + .clasprc.json
as a documented fallback if the experimental --adc flag proves unreliable.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add GitHub Actions clasp deployment implementation plan

Step-by-step plan covering GCP WIF setup, GitHub Environments config,
deploy.yml creation, actionlint validation, and end-to-end verification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add deploy workflow with WIF authentication

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use npx clasp to ensure local binary resolves in CI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* adding visible test change

* removing test change

* updating worklow for debugging purposes

* rmoving worklow for debugging purposes

* fix: use access_token format to bridge WIF credentials into clasp

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: write clasprc in v3 format (tokens.default) not legacy v1 format

* fix: clear GOOGLE_APPLICATION_CREDENTIALS in deploy step and add authorized_user type to clasprc

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: provide all required UserRefreshClient fields in clasprc to pass fromJSON validation

* test change

* docs: update deploy design doc with implementation history and current status

WIF + service account approach is blocked by intentional Google limitation:
Apps Script API does not support service account authentication. Documents
all attempts made, root causes found, and promotes fallback (custom OAuth +
stored .clasprc.json) as the recommended path forward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove deploy.yml pending fallback auth implementation

WIF + service account approach is blocked by Apps Script API's intentional
rejection of service accounts. Last working version preserved at 407df59.
Restore with: git show 407df59:.github/workflows/deploy.yml > .github/workflows/deploy.yml

See docs/plans/2026-02-19-github-actions-clasp-deploy-design.md for full history.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace callGeminiAPI with GeminiRequest options object interface (#15)

* docs: add Gemini API interface design doc

Replaces AIContext discriminated union with a GeminiRequest options
object. Separates Drive I/O from the HTTP adapter, exposes buildGeminiPayload
as a pure testable function, and adds hooks for tools/function calling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add Gemini API interface implementation plan

7-task TDD plan covering types, api.ts rewrite, fetchAndEncodeFile,
getAIContext removal, and runBatchAI update.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace callGeminiAPI with GeminiRequest options object interface

- Add GeminiRequest, GeminiInlineData, GeminiFunctionDeclaration,
  GeminiGenerationConfig interfaces to types.ts
- Remove AIContext, TextContext, FileContext from types.ts
- Split api.ts into buildGeminiPayload (pure) + callGeminiAPI(req)
- Add fetchAndEncodeFile to drive.ts; remove Drive I/O from api.ts
- Remove getAIContext from utils.ts; inline skip logic in runBatchAI
- Update all tests accordingly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove as-any cast in callGeminiAPI, use CONFIG.MODEL_NAME in test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: cover createSeededRandom no-seed branch, fix utils coverage threshold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: upgrade GeminiRequest.inlineData to array for multi-file support (#17)

* feat: upgrade GeminiRequest.inlineData to array for multi-file support

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: assert full inline_data object shape in multi-item test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add SSI Sheets custom function (text-only) (#18)

* docs: add GEMINI custom function design doc

Covers GeminiRequest.inlineData array upgrade, customFunctions.ts
module, input normalization, tool registry, wiring, and test plan.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add GEMINI custom function implementation plan

5-task TDD plan: inlineData array upgrade, custom function with tests,
Rollup wiring, coverage threshold, final verification.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add GEMINI custom function with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: address quality review issues in GEMINI custom function

- Validate extractId return value before fetchAndEncodeFile to surface
  actionable Drive URL errors rather than an opaque DriveApp exception
- Replace unsafe (e as Error) cast with instanceof guard in catch block
- Export TOOL_REGISTRY so tests can inject entries; add happy-path
  toolNames test verifying the declaration reaches the API payload

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: wire GEMINI custom function into the Apps Script bundle

Re-export GEMINI from index.ts and add a global function stub in the
Rollup footer so Apps Script can discover and invoke the custom function.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add coverage thresholds for customFunctions.ts

Observed: 96% statements, 90.9% branches, 100% functions.
Thresholds set to 90/85/100 per design doc (≈5 points below observed),
following the same margin used for api.ts, utils.ts, and drive.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: rename GEMINI custom function to SSI

Google Sheets ships a built-in GEMINI function that shadows any
user-defined function with the same name, preventing custom functions
from appearing in autocomplete or executing correctly.

Rename the function and all error string prefixes to SSI throughout:
customFunctions.ts, index.ts, rollup.config.js, and the test file.
Usage: =SSI(userTexts, inlineData?, systemPrompt?, toolNames?)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add @customfunction JSDoc to SSI footer stub

Google Sheets only registers a function as a custom function when the
@customfunction tag appears in a JSDoc comment directly on the global
function declaration. The TypeScript-level JSDoc is compiled away by
Rollup, so the tag must be added explicitly to the footer stub.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: document @customfunction JSDoc requirement for footer stubs

- CLAUDE.md: expand the two-step wiring rule into a three-step rule for
  custom functions, explaining that @customfunction must appear on the
  global stub in rollup.config.js (not just in the TypeScript source)
- CLAUDE.md: correct the PropertiesService constraint — it IS available
  in custom functions after the add-on has been authorized
- CLAUDE.md: update module dependency graph to include customFunctions.ts
- rollup.config.js: add a prominent comment in the footer block warning
  that custom function stubs require a @customfunction JSDoc tag

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: remove inlineData parameter from SSI custom function

Drive file access is not available from AuthMode.CUSTOM_FUNCTION context.
Defer inlineData support to feature/ssi-inline-data.

- SSI(): remove inlineData? parameter; signature is now (userTexts, systemPrompt?, toolNames?)
- fetchAndEncodeFile: revert to DriveApp (REST API version was custom-function-only)
- rollup.config.js: update SSI footer stub to 3-parameter signature
- Tests: remove inlineData mocks and tests; revert drive.test.ts to DriveApp pattern
- CLAUDE.md: remove AuthMode.CUSTOM_FUNCTION limitation notes (belong in inlineData PR)

* test: cover null userTexts path in flattenArg

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* quick docs update

* feat: add inlineData parameter to SSI custom function (deferred — Drive auth blocked) (#19)

* fix: replace DriveApp with Drive REST API in fetchAndEncodeFile

DriveApp is unavailable in Google Apps Script custom function execution
contexts, causing "permissions not sufficient" errors when SSI() is
called with an inlineData argument. UrlFetchApp and ScriptApp.getOAuthToken()
work in all contexts and are the GAS-documented pattern for this case.

fetchAndEncodeFile now makes two Drive REST API calls via UrlFetchApp:
one for file metadata (mimeType + size) and one for file content.
Error responses from each call surface a descriptive message.

Update drive.test.ts to mock the Drive REST API calls instead of DriveApp,
and add fallback-message tests to restore 100% branch coverage on drive.ts.
Update customFunctions.test.ts to use mockDriveApiFile() helper and a
getGeminiPayload() helper that locates the Gemini call by URL regardless
of how many Drive calls precede it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: document DriveApp restriction in custom function contexts

Custom functions cannot use DriveApp or other OAuth-requiring services.
Document the UrlFetchApp + ScriptApp.getOAuthToken() pattern as the
correct alternative, with a pointer to fetchAndEncodeFile as an example.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: clarify Drive access limitation in custom function context

Custom functions run in AuthMode.CUSTOM_FUNCTION, which scopes
ScriptApp.getOAuthToken() to spreadsheets.currentonly only. Drive API
calls with this token return 401 — not a permissions error, but an auth
scope mismatch that cannot be resolved without a service account.

- fetchAndEncodeFile: guard for null/insufficient token with a clear
  error pointing users to the Run AI menu tool
- customFunctions.ts: revert the auth-error heuristic (no longer needed
  now that the error surfaces directly from fetchAndEncodeFile)
- drive.ts / CLAUDE.md: accurately document the AuthMode.CUSTOM_FUNCTION
  constraint, why ScriptApp.getOAuthToken() doesn't help, and the
  service-account alternative with its UX trade-off
- drive.test.ts: update null-token test assertion to match new message

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restore inlineData parameter in SSI rollup footer stub

The cleanup commit on feature/ssi-custom-function removed inlineData from
the footer stub, but feature/ssi-inline-data re-adds it to customFunctions.ts.
The stub was out of sync, silently dropping the inlineData argument.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Revert "feat: add inlineData parameter to SSI custom function (deferred — Drive auth blocked) (#19)"

This reverts commit 4882a8ba49bd628a74e823295027a8fefa417c42.

* missing docs

* docs: add design for consolidating Gemini call path

Introduces invokeGemini as the single production entry point for Gemini
calls, moves flattenArg to utils.ts, and extracts TOOL_REGISTRY to its
own tools.ts module.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update consolidation plan to include runInference handler

Adds inference.ts module with runInference — a unified handler that
normalizes cell inputs, encodes Drive files, calls invokeGemini, and
writes the result to an output cell. Simplifies runBatchAI loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: move flattenArg to utils.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: extract TOOL_REGISTRY to tools.ts

* feat: add invokeGemini as single Gemini entry point

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: update api.test.ts header comment to reflect PropertiesService mock

* docs: update inference handler to return string|null, no SpreadsheetApp dependency

* feat: add runInference unified inference handler

Implements runInference in src/server/inference.ts — a Sheets-free
function that normalizes raw cell values (scalar or 2D range) into a
Gemini request via invokeGemini, returning the response string, an
"Error: ..." string on failure, or null to signal an empty-row skip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: flattenArg returns [] for empty string scalar, remove redundant filter in runInference

* refactor: SSI delegates to invokeGemini, imports from utils and tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: runBatchAI loop delegates to runInference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: consolidate Gemini call path (#21)

* docs: add design for consolidating Gemini call path

Introduces invokeGemini as the single production entry point for Gemini
calls, moves flattenArg to utils.ts, and extracts TOOL_REGISTRY to its
own tools.ts module.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update consolidation plan to include runInference handler

Adds inference.ts module with runInference — a unified handler that
normalizes cell inputs, encodes Drive files, calls invokeGemini, and
writes the result to an output cell. Simplifies runBatchAI loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: move flattenArg to utils.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: extract TOOL_REGISTRY to tools.ts

* feat: add invokeGemini as single Gemini entry point

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: update api.test.ts header comment to reflect PropertiesService mock

* docs: update inference handler to return string|null, no SpreadsheetApp dependency

* feat: add runInference unified inference handler

Implements runInference in src/server/inference.ts — a Sheets-free
function that normalizes raw cell values (scalar or 2D range) into a
Gemini request via invokeGemini, returning the response string, an
"Error: ..." string on failure, or null to signal an empty-row skip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: flattenArg returns [] for empty string scalar, remove redundant filter in runInference

* refactor: SSI delegates to invokeGemini, imports from utils and tools

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: runBatchAI loop delegates to runInference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: dynamic column mapping for Run AI (#22)

* docs: add dynamic column mapping design for runBatchAI

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add dynamic column mapping implementation plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: make runInference driveLinks and systemPrompt optional params

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: update stale JSDoc and test descriptions for optional runInference params

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add resolveColumns pure helper to utils

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace AIMode/ColumnMap with RunConfig, rewrite runBatchAI, add getSheetHeaders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: guard empty sheet in runBatchAI, sync rollup footer stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: delete dialog.ts — replaced by sidebar config panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace AI modal with inline sidebar config panel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove dead markup in sidebar, final verification

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: replace checkbox lists with pill tags, reset run button after success

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: port pill tag UX to system prompt and output column fields

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: sidebar refactor design — client TS/CSS split with RunConfig autopopulation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: sidebar refactor implementation plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add google.d.ts type stub and DOM lib for client code

Add src/client/google.d.ts declaring the GoogleScriptRun interface and
google.script.run global for sidebar client TypeScript. Add DOM to
tsconfig lib to enable browser API types. Fix MimeType global collision
in drive.ts by casting through unknown to GoogleAppsScript.Base.MimeType.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use tsconfig.client.json for client types, fix withFailureHandler type

- Revert DOM lib from shared tsconfig.json to avoid MimeType collision with GAS
- Add tsconfig.client.json that extends tsconfig.json with DOM lib for src/client
- Revert GASMimeType cast workaround in drive.ts; MimeType enum works directly now
- Update typecheck script to run both tsc --noEmit and tsc -p tsconfig.client.json --noEmit
- Add jest transform entry to use tsconfig.client.json for src/client source files
- Fix withFailureHandler type: GAS always passes Error, not Error | string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: extract Sidebar CSS to sidebar.css, drop Google Fonts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add sidebar.ts skeleton with exported stubs

Adds src/client/sidebar.ts with empty exported stubs for all pure
helper functions, using void expressions to satisfy noUnusedParameters.
Also excludes src/client from the base tsconfig so DOM-dependent client
code is only checked under tsconfig.client.json (which includes the DOM lib).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: correct import path in google.d.ts

* feat: implement buildTagList with tests

Add jest-environment-jsdom dependency and sidebar.test.ts with 4 TDD
tests for buildTagList. Implement buildTagList to render .tag buttons,
support pre-selection, toggle .selected on click, and clear container
on re-render.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: pin jest-environment-jsdom to jest 29 major version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement buildSingleTagList with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement handleRowRangeChange with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement applyPreset with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement assembleRunConfig with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: applyPreset reveals new-col-input for __new__ preset; add sidebar coverage threshold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restrict tsconfig types to prevent jsdom MimeType collision

Add "types": ["google-apps-script", "jest"] to tsconfig.json so that
@types/jsdom (pulled in by jest-environment-jsdom) no longer bleeds its
DOM MimeType interface into server-side compilation, eliminating the
need for the (MimeType as unknown as GoogleAppsScript.Base.MimeType)
casts in drive.ts.

Add tsconfig.test.client.json (DOM lib, no jsdom in types) and wire it
into jest.config.cjs via a per-pattern transform for sidebar.test.ts,
so the jsdom-environment test keeps its HTMLElement / document types
without affecting server compilation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: declare global in google.d.ts and clear inherited exclude in client tsconfigs

google.d.ts used a bare `declare const google` in a module file (file had a
top-level import), scoping it locally rather than globally. Wrapping in
`declare global {}` makes it a true ambient global visible to sidebar.ts.

tsconfig.client.json and tsconfig.test.client.json both extended the base
tsconfig which excludes src/client. Neither overrode `exclude`, so their
`include` globs for src/client/**/*.ts were silently ignored. Adding
`"exclude": []` clears the inherited exclusion and lets the files be compiled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add showAIPanel, hideAIPanel, dispatchTool, runAI, init

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: convert Sidebar.html to build template with id-wired buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add inlineSidebarHtml Rollup plugin and client build config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove manual Sidebar.html copy from build scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove redundant tsconfig.test.client.json, consolidate to tsconfig.client.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress sourcemap warning in client Rollup config

Client JS is inlined into dist/Sidebar.html and the chunk is deleted,
so sourcemaps serve no purpose. Disable them in the typescript plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: sidebar TypeScript/CSS refactor with RunConfig autopopulation (#23)

* docs: sidebar refactor design — client TS/CSS split with RunConfig autopopulation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: sidebar refactor implementation plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add google.d.ts type stub and DOM lib for client code

Add src/client/google.d.ts declaring the GoogleScriptRun interface and
google.script.run global for sidebar client TypeScript. Add DOM to
tsconfig lib to enable browser API types. Fix MimeType global collision
in drive.ts by casting through unknown to GoogleAppsScript.Base.MimeType.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use tsconfig.client.json for client types, fix withFailureHandler type

- Revert DOM lib from shared tsconfig.json to avoid MimeType collision with GAS
- Add tsconfig.client.json that extends tsconfig.json with DOM lib for src/client
- Revert GASMimeType cast workaround in drive.ts; MimeType enum works directly now
- Update typecheck script to run both tsc --noEmit and tsc -p tsconfig.client.json --noEmit
- Add jest transform entry to use tsconfig.client.json for src/client source files
- Fix withFailureHandler type: GAS always passes Error, not Error | string

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: extract Sidebar CSS to sidebar.css, drop Google Fonts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add sidebar.ts skeleton with exported stubs

Adds src/client/sidebar.ts with empty exported stubs for all pure
helper functions, using void expressions to satisfy noUnusedParameters.
Also excludes src/client from the base tsconfig so DOM-dependent client
code is only checked under tsconfig.client.json (which includes the DOM lib).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: correct import path in google.d.ts

* feat: implement buildTagList with tests

Add jest-environment-jsdom dependency and sidebar.test.ts with 4 TDD
tests for buildTagList. Implement buildTagList to render .tag buttons,
support pre-selection, toggle .selected on click, and clear container
on re-render.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: pin jest-environment-jsdom to jest 29 major version

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement buildSingleTagList with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement handleRowRangeChange with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement applyPreset with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: implement assembleRunConfig with tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: applyPreset reveals new-col-input for __new__ preset; add sidebar coverage threshold

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: restrict tsconfig types to prevent jsdom MimeType collision

Add "types": ["google-apps-script", "jest"] to tsconfig.json so that
@types/jsdom (pulled in by jest-environment-jsdom) no longer bleeds its
DOM MimeType interface into server-side compilation, eliminating the
need for the (MimeType as unknown as GoogleAppsScript.Base.MimeType)
casts in drive.ts.

Add tsconfig.test.client.json (DOM lib, no jsdom in types) and wire it
into jest.config.cjs via a per-pattern transform for sidebar.test.ts,
so the jsdom-environment test keeps its HTMLElement / document types
without affecting server compilation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: declare global in google.d.ts and clear inherited exclude in client tsconfigs

google.d.ts used a bare `declare const google` in a module file (file had a
top-level import), scoping it locally rather than globally. Wrapping in
`declare global {}` makes it a true ambient global visible to sidebar.ts.

tsconfig.client.json and tsconfig.test.client.json both extended the base
tsconfig which excludes src/client. Neither overrode `exclude`, so their
`include` globs for src/client/**/*.ts were silently ignored. Adding
`"exclude": []` clears the inherited exclusion and lets the files be compiled.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add showAIPanel, hideAIPanel, dispatchTool, runAI, init

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: convert Sidebar.html to build template with id-wired buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add inlineSidebarHtml Rollup plugin and client build config

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove manual Sidebar.html copy from build scripts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: remove redundant tsconfig.test.client.json, consolidate to tsconfig.client.json

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: suppress sourcemap warning in client Rollup config

Client JS is inlined into dist/Sidebar.html and the chunk is deleted,
so sourcemaps serve no purpose. Disable them in the typescript plugin.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: split sidebar into pure helpers and GAS-coupled entry point

Move showAIPanel, hideAIPanel, dispatchTool, runAI, and init() from
sidebar.ts into a new sidebar-entry.ts, mirroring the server-side
pattern where index.ts (GAS-coupled) is excluded from coverage and
pure modules (api.ts, utils.ts, etc.) hold high thresholds.

sidebar-entry.ts is excluded from collectCoverageFrom; sidebar.ts now
hits 100% statements/functions and 86% branches against its existing
95/81/95 thresholds with no istanbul-ignore annotations needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* adding npm fixes

* docs: sidebar entry point testing design — fixture reuse + callback coverage

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: sidebar entry point testing implementation plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add shared sidebar DOM fixture module

FULL_SIDEBAR_HTML reads from src/Sidebar.html at test time so fixtures
stay structurally in sync with the template without manual drift.
Adds testPathIgnorePatterns and helpers transform rule to jest.config.cjs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: sidebar tests use shared fixture module

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: scope Node types to fixture file, tighten tsconfig.client.json include

Use /// <reference types="node" /> in sidebar-fixtures.ts instead of
adding "node" to tsconfig.client.json types array, preventing a potential
MimeType collision with google-apps-script types. Replace __tests__/**/*.ts
glob with explicit paths so server-side test files are not type-checked
under the DOM+GAS environment.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: export sidebar-entry functions for testing

* test: hideAIPanel and showAIPanel callback tests

* test: dispatchTool loading state and runTool callback tests

* test: runAI assembly, runBatchAI dispatch, and callback tests

* test: sidebar-entry callback tests (hideAIPanel, showAIPanel, dispatchTool, runAI)

Export showAIPanel, hideAIPanel, dispatchTool, runAI from sidebar-entry.ts
for testing. Add __tests__/sidebar-entry.test.ts with 16 tests covering
success/failure callbacks via mock capture pattern. Fix tsconfig.client.json
rootDir to cover __tests__/ files without rootDir conflict.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: include sidebar-entry.ts in coverage with per-file thresholds

Remove exclusion from collectCoverageFrom; add threshold block at
observed actuals minus 5 points (82/52/52 stmts/branches/fns).
Low branches and functions reflect init()'s untested addEventListener
wiring — init() runs at module load before beforeEach sets up the DOM.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update sidebar-entry.ts header — file is now partially covered

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update CLAUDE.md with client build pipeline and sidebar test rig (#24)

- Document two-config Rollup setup: Config 2 compiles sidebar-entry.ts
  and uses inlineSidebarHtml plugin to assemble dist/Sidebar.html
- Add client module dependency graph (sidebar-entry.ts, sidebar.ts,
  google.d.ts, sidebar.css, Sidebar.html template)
- Document tsconfig.client.json: rootDir ".", DOM lib, precise includes;
  typecheck now runs both tsconfigs
- Document MimeType collision risk: never add "node" to tsconfig types;
  use triple-slash directive per-file instead
- Document client-side google.script.run mock callback capture pattern
- Document shared DOM fixture infrastructure: sidebar-fixtures.ts reads
  src/Sidebar.html at test time to prevent drift
- Clarify coverage boundaries: index.ts excluded, sidebar-entry.ts
  included with lower thresholds (init() runs before DOM is available)
- Reference new design docs for testing rationale

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add component and panel test directories to jest and tsconfig

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add client navigation types (PanelId, NavigationContext, Panel)

* feat: add Router with push/pop navigation stack and per-panel state serialization

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add services module wrapping google.script.run as Promises with header cache

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update CLAUDE.md client architecture to reflect services.ts as GAS boundary

* feat: add TagList component — multi-select, self-contained DOM, getValue()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add SingleTagList component — single-select, owns new-column input, handles custom savedState values

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add RowRange component — radio + range inputs, self-contained, getValue()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add missing RowRange tests and unique radio group name per instance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add LockableField component — locked-by-default input/textarea with unlock toggle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add ToolListPanel with navigate callbacks and tool dispatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add missing ToolListPanel button-mapping tests, double-flush async assertions, use globalThis.alert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: set btn.disabled in ToolListPanel loading state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add ConfigureAIRunPanel with component-based form, savedState, and Promise-based GAS calls

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: assert button text restoration in ConfigureAIRunPanel success/failure tests

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: export SavedState from ConfigureAIRunPanel; use typed cast in test helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add RecipesListPanel and DocumentSummarizationPanel stubs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: migrate sidebar to panel-router architecture; cleanup old code

- Simplify Sidebar.html to minimal <div id="app"> shell
- Replace sidebar-entry.ts with thin Router init (all logic in panels/components)
- Delete sidebar.ts, old sidebar tests, sidebar-fixtures.ts
- Update jest.config.cjs: remove old transforms, add coverage thresholds for new files
- Update tsconfig.client.json: remove deleted test file entries
- Improve router/services tests to cover throw paths and runTool failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: client panel-router architecture with component-based sidebar (#26)

* refactor: derive SavedState from RunConfig to eliminate field duplication

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* reordering menu buttons

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: recipe panels system with Document Summarization (#27)

* docs: add recipe panels design document

Covers RecipeParams/PrepRecipeParams/PrepRecipeResult type separation,
generic RecipePanel with RecipePrepCook component, RECIPES registry
pattern, server-side prepRecipe orchestrator, and full testing strategy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add recipe panels implementation plan

12-task TDD plan covering shared types, sheet helpers, prepRecipe server
function, services wrapper, LockableField onUnlock, RecipePrepCook
component, RecipePanel, RecipesListPanel, and coverage thresholds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add RecipeFieldConfig, RecipeParams, PrepRecipeParams, PrepRecipeResult types

* feat: add 'recipe' to PanelId, add RecipeDefinition interface

* feat: add findOrCreateColumn and writeColumn helpers to utils

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: update utils.ts header comment; guard writeColumn against empty array

- Replace misleading "no dependency on Apps Script globals" header with
  accurate language about GAS objects received as arguments vs. singletons.
- Clean up makeSheet helper in tests: replace identity map with headers.slice().
- Guard writeColumn against empty values array to prevent GAS runtime error
  when getRange is called with numRows=0; add corresponding test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add prepRecipe server function and rollup global stub

Implements the prepRecipe() server function that accepts PrepRecipeParams,
writes Drive file URLs, system prompt, user prompts, and output column
headers to the active sheet using findOrCreateColumn/writeColumn helpers,
and returns a PrepRecipeResult with the populated row range and column names.
Also adds the matching global stub in rollup.config.js so Apps Script can
discover the function, and fixes a pre-existing PanelId type gap by adding
"document-summarization" to the union.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: revert out-of-scope document-summarization PanelId addition

Task 4 subagent incorrectly re-added "document-summarization" to PanelId
to suppress typecheck errors in sidebar-entry.ts. Those errors are expected
and will be resolved in Task 11 when sidebar-entry.ts is updated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add prepRecipe service wrapper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use PrepRecipeParams type in google.d.ts stub

Consistent with how runBatchAI uses RunConfig — unknown breaks type-level
contract for direct callers of google.script.run.prepRecipe.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add onUnlock callback to LockableField

* feat: add RecipePrepCook component with prep/cook state machine

* feat: add RECIPES registry with Document Summarization entry

* feat: add generic RecipePanel driven by RecipeParams

* feat: replace RecipesListPanel stub with data-driven RECIPES implementation

* fix: suppress double-alert on silent validation cancellation

When buildPrepParams() returns null (drive folder URL empty), recipe.ts
now rejects with null instead of new Error("cancelled"). RecipePrepCook
checks err !== null before alerting, so only the validation alert from
buildPrepParams fires. Test updated to assert alertMock called exactly once.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: register RecipePanel in router; remove DocumentSummarizationPanel stub

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add coverage thresholds for recipe-prep-cook, recipe, recipes-list

Also adds explicit return types to onPrep/onCook arrow functions in recipe.ts
to satisfy the ESLint explicit-function-return-type rule.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use single tsconfig in jest transform to avoid ts-jest ConfigSet caching bug

ts-jest's TsJestTransformer._cachedConfigSets is a static array keyed by the
global Jest config object reference, not by per-transform tsconfig options. On
CI (1 worker), a server transformer running first would cache ConfigSet(tsconfig.json,
no DOM), causing all subsequent client transforms to reuse the wrong tsconfig and
fail with "Cannot find name 'document'". Consolidating to a single rule using
tsconfig.client.json eliminates the conflict — server code compiles cleanly with
DOM types in scope since it never references them.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update CLAUDE.md to reflect PR #26/#27 changes (#28)

- Client module graph: add types.ts, recipes.ts, expand panels and
  components lists with all new files from the panel-router and
  recipe-panels PRs
- server/utils.ts: add findOrCreateColumn and writeColumn to description
- TypeScript Configuration: add jest.config.cjs single-tsconfig note
  explaining the ts-jest ConfigSet caching bug workaround
- Coverage section: fix stale sidebar-entry.ts description (old exported
  functions no longer exist; file is now fully excluded from collection)
- Remove stale reference to sidebar-entry-testing-design.md

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: sidebar UI polish — cards, hierarchy, type cleanup, CLIP wrap (#29)

* feat: sidebar UI polish — cards, hierarchy, type cleanup, CLIP wrap

- Recipe section cards: each param group (Drive Folder, System Prompt,
  User Prompt, Output Column) rendered in a bordered card with an
  ALL-CAPS section title, giving clear visual separation
- LockableField inline layout: single-line fields render label + input
  + lock button on one row; multiline keeps header-row + textarea layout
- Recipe button hierarchy: description drops below name as smaller
  secondary text using flex-column .tool-btn-text wrapper
- Recipe panel header now shows icon + name (e.g. "📄 Document
  Summarization") instead of the generic "Recipe" title
- RecipeDefinition.params typed as RecipeParams (was unknown); panel
  receives full RecipeDefinition so name/icon flow naturally — no new
  interface needed
- RecipesListPanel navigates with full RecipeDefinition, not just params
- Configure AI Run panel renamed to "▶️ Run AI Inference" to match the
  tool-list button label
- "✨ More recipes coming soon…" stub added to recipes list (dashed
  border, non-interactive)
- findOrCreateColumn / writeColumn accept optional wrapStrategy param;
  all prepRecipe call sites pass SpreadsheetApp.WrapStrategy.CLIP so
  newly created columns don't blast users with wrapped text

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: invalidate header cache after prepRecipe succeeds

When a user opened Run AI Inference on an empty sheet, getSheetHeaders
cached the empty result. Subsequent navigations to the same panel after
a recipe prep (which created new columns) returned the stale [] cache,
showing "No columns found" despite headers now existing.

Fix: call invalidateHeaderCache() inside prepRecipe's success handler so
the cache is always fresh after a prep writes new columns to the sheet.

Added a test that primes the cache with [], runs prepRecipe, then
verifies the next getSheetHeaders hits GAS rather than returning [].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: cover wrapStrategy branches in findOrCreateColumn and writeColumn

The new optional wrapStrategy param introduced two uncovered branches,
dropping utils.ts below the 93% branch threshold. Added one test per
function verifying that setWrapStrategy is called on the correct range
when the param is provided.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: consistent button design language across recipe and run AI panels

- Add .btn-outline (blue border, blue text, white fill) for Prep Recipe —
  signals "first step" without looking like a cancel or destructive action
- Use .btn-run (filled blue) for Cook — primary culminating action that
  lights up after prep completes
- Remove Cancel button from Run AI Inference panel; the back button in
  the header already covers this and the redundancy created confusion
- Add .btn-outline:disabled state (muted border/text, no pointer)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Adding a arrow

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: remove header cache from services.ts (#30)

The cache only saved one GAS round-trip per sidebar open but introduced
correctness bugs — it was poisoned by the first empty lookup, and it had
no invalidation path when the user switched sheets mid-session. Since
GAS round-trips are cheap relative to the Prep/Cook workflow, the
complexity isn't worth it. getSheetHeaders() now always fetches live.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: rewrite README for contributor onboarding, add google.d.ts step to CLAUDE.md (#31)

Replaces the stale, overly granular README with a focused developer guide covering
prerequisites, setup, key commands, build pipeline, architecture, and testing patterns.
Cuts file trees, module tables, and end-user content that belongs elsewhere.

Also adds a third step to the CLAUDE.md "expose a new function" checklist — updating
google.d.ts when adding client-callable server functions.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add .worktrees/ to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* updating model

* Feature/gemini tools (#32)

* chore: add .worktrees/ to .gitignore

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add server/types.ts with Gemini API types and GeminiTool union

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: fix misleading GeminiTool comment (type is exported)

* feat: add ToolId to shared types, add tools field to RunConfig/PrepRecipeParams/PrepRecipeResult

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add DriveFileInfo to server/types.ts (missed during shared cleanup)

* refactor: move RecipeParams and RecipeFieldConfig to client/types.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: fix imports after type reorganization

Routes server imports (AppConfig, GeminiInlineData, GeminiRequest,
GeminiFunctionDeclaration, DriveFileInfo) from shared/types to
server/types, and client RecipeParams from shared/types to client/types.
Adds temporary casts and SavedState adjustment to keep typecheck clean
until Tasks 5/7/8 refactor the dependent logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace TOOL_REGISTRY with unified Record<ToolId, GeminiTool>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add client/tools.ts with TOOL_CATALOG and ToolCatalogEntry

Introduces the client-side tool catalog that provides display metadata
(id, name, description) for tools shown in the sidebar TagList. Hardcoded
at build time — no RPC needed since the tool list is static compiled code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: update buildGeminiPayload to resolve ToolId[] via TOOL_REGISTRY

Replaces the old flat function_declarations pass-through with a two-phase
split: grounding tools produce { [id]: {} } entries and function tools
produce a { function_declarations: [...] } entry, all assembled into the
correct Gemini REST payload shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: update SSI to validate ToolId and support all tool types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add tools parameter to runInference

* test: remove stray tools assertion from inlineData test

* feat: thread tools through runBatchAI and prepRecipe

* feat: extend TagList to support label/value items alongside plain strings

* feat: add tools TagList to ConfigureAIRunPanel

* test: fix coverage thresholds and improve test quality

* docs: add gemini-tools design doc and implementation plan

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Feature/gemini tools (#33)

* feat: add server/types.ts with Gemini API types and GeminiTool union

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: fix misleading GeminiTool comment (type is exported)

* feat: add ToolId to shared types, add tools field to RunConfig/PrepRecipeParams/PrepRecipeResult

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add DriveFileInfo to server/types.ts (missed during shared cleanup)

* refactor: move RecipeParams and RecipeFieldConfig to client/types.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: fix imports after type reorganization

Routes server imports (AppConfig, GeminiInlineData, GeminiRequest,
GeminiFunctionDeclaration, DriveFileInfo) from shared/types to
server/types, and client RecipeParams from shared/types to client/types.
Adds temporary casts and SavedState adjustment to keep typecheck clean
until Tasks 5/7/8 refactor the dependent logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: replace TOOL_REGISTRY with unified Record<ToolId, GeminiTool>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add client/tools.ts with TOOL_CATALOG and ToolCatalogEntry

Introduces the client-side tool catalog that provides display metadata
(id, name, description) for tools shown in the sidebar TagList. Hardcoded
at build time — no RPC needed since the tool list is static compiled code.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: update buildGeminiPayload to resolve ToolId[] via TOOL_REGISTRY

Replaces the old flat function_declarations pass-through with a two-phase
split: grounding tools produce { [id]: {} } entries and function tools
produce a { function_declarations: [...] } entry, all assembled into the
correct Gemini REST payload shape.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: update SSI to validate ToolId and support all tool types

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add tools parameter to runInference

* test: remove stray tools assertion from inlineData test

* feat: thread tools through runBatchAI and prepRecipe

* feat: extend TagList to support label/value items alongside plain strings

* feat: add tools TagList to ConfigureAIRunPanel

* test: fix coverage thresholds and improve test quality

* docs: add gemini-tools design doc and implementation plan

* feat: add url_context and code_execution grounding tools

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update CLAUDE.md to reflect gemini-tools architecture (#34)

* docs: update CLAUDE.md to reflect gemini-tools architecture (PRs #32/#33)

Updates module dependency graph and adds a dedicated Tool System section
covering ToolId, TOOL_REGISTRY, TOOL_CATALOG, GeminiTool discriminated
union, payload assembly, and the three-file checklist for adding new tools.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add GitHub PR workaround for TLS certificate issue

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* Feature: grounding metadata presentation and rich cell text (#35)

* feat: add GeminiResponse types to server/types.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: callGeminiAPI returns GeminiResponse

Update callGeminiAPI to return GeminiResponse instead of string.
Assembles text from all text parts, extracts code execution pairs,
and captures groundingMetadata from the REST response. Update
invokeGemini return type and apply .text at call sites in customFunctions
and inference that still need string; invokeGemini test updated in next task.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: update invokeGemini tests for GeminiResponse return type

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: runInference returns GeminiResponse | null

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add grounding metadata enrichment design and implementation plan

* Feature/grounding metadata presentation refinements (#36)

* docs: add grounding metadata presentation design

* docs: add grounding metadata presentation implementation plan

* feat: add GeminiGroundingSupport type and RunConfig.includeGrounding

Also updates SavedState in ConfigureAIRunPanel to exclude includeGrounding
from the Required<Omit<...>> so the type remains consistent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add getCitations pure helper to api.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add getUngroundedSpans pure helper to api.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: correct Span index boundaries and add edge case tests for getUngroundedSpans

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add getAllSources pure helper to api.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add includeGrounding checkbox to ConfigureAIRunPanel

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: code quality improvements to includeGrounding checkbox

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: exercise event-driven label update in includeGrounding test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: renderInference/renderGrounding in runBatchAI; RichTextValue output

Add renderInference and renderGrounding helper functions to src/server/index.ts.
Update runBatchAI to write RichTextValue with inline citation hyperlinks via
setRichTextValue, and optionally write a {outputCol}_grounding column with
source attribution, search queries, unverified spans, or code execution output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: safe citation merging, per-row error fallback, and source hyperlink scoping in index.ts

- Merge overlapping citation ranges before calling setLinkUrl to prevent Apps Script exceptions
- Add comment noting code_execution + google_search co-activation constraint on text offsets
- Wrap render/write block in per-row try/catch with plain-text fallback so one bad row doesn't abort the batch
- Scope source title hyperlinking to the Sources section only, avoiding false matches elsewhere in fullText
- Push newly created output column name to headers array, matching the grounding column pattern

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add grounding presentation refinements plan

* docs: add markdown rendering task to refinements plan

* refactor: remove getUngroundedSpans — Unverified section dropped from grounding column

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add rich-text.ts — pure CellContent/TextRange builders for inference and grounding cells

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: use running cursor for duplicate source titles; document merge URI decision; tighten test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: strip api.ts to pure HTTP adapter — getCitations/getAllSources moved to rich-text.ts

- Remove Citation interface, getCitations, getAllSources from api.ts
- Remove GeminiGroundingSupport from api.ts imports (no longer needed there)
- Export getCitations and getAllSources from rich-text.ts (were private)
- Update index.ts import to pull getCitations/getAllSources from rich-text.ts
- Remove getCitations and getAllSources describe blocks from api.test.ts
- Remove unused GeminiResponse type import from api.test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: toCellValue in index.ts; renderInference/renderGrounding replaced by pure rich-text.ts builders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: move grounding checkbox into Tools section with conditional visibility

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add per-file coverage threshold for rich-text.ts

New file src/server/rich-text.ts was added without a corresponding
coverageThreshold entry in jest.config.cjs. Adds thresholds based on
observed coverage (statements 99.29%, branches 87.15%, functions 100%),
set ~5 points below observed per project convention.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test: add retrievedContext coverage; document codePairs early-return design decision

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: rewrite refinements plan to incorporate rich-text.ts architecture

* fix: use camelCase executableCode/codeExecutionResult keys — Gemini REST API returns camelCase

* style: code badge for grounding column name; clean up checkbox label

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: allow deselecting tags in SingleTagList (#37)

Clicking an already-selected tag in system prompt or AI output fields
had no effect because selectOnly() always re-added the selected class.
Now captures wasSelected before clearing, matching the toggle semantics
used by TagList.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: rewrite markdown parser with line-oriented approach (#39)

* refactor: rewrite parseMarkdown with line-oriented approach

Fixes bullet list (* and -) misparse and adds [text](url) inline link
support. processInline helper separates structural stripping (headings,
bullets) from inline pattern matching, making the parser easier to
reason about and remove if needed. posMap citation remapping preserved.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: guard zero-width url ranges and handle ) in URLs

processInline: skip pushing a url TextRange when link text is empty
(Sheets API rejects zero-width ranges). Also use lastIndexOf to find
the closing ) so URLs containing literal parentheses (e.g. Wikipedia
links) are not truncated.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: add markdown parser rewrite design plan

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: normalise absent startIndex to 0 in getCitations (#40)

Gemini omits startIndex from grounding segment when its value is 0
(proto3 default). This caused setRichTextValue to receive an undefined
index, throw, and silently fall back to plain text — also dropping the
grounding column write. Normalise with ?? 0 at the parse boundary.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: add apply markdown checkbox to Configure AI Run panel (#41)

* feat: add rawOutput checkbox to Configure AI Run panel

When checked, runBatchAI writes result.text directly via setValue,
bypassing markdown parsing and rich text formatting. The grounding
column is unaffected. Saved state persists the checkbox value across
navigation. Renames .grounding-hint CSS class to .checkbox-option
so both checkboxes share the same styling.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: replace rawOutput with applyMarkdown, decouple grounding write

Inverts the checkbox semantics: unchecked (default) writes plain text,
checked applies markdown parsing and rich text formatting. Grounding
column now writes independently of the formatting setting — a user can
have raw output in the inference column while still getting the
grounding column.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: move apply markdown checkbox into output column section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* small verbiage change

* ?

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat: migrate to Editor Add-on deployment pipeline (#42)

* chore: track .clasp.json as stable add-on config

* chore: remove container-bound dev/prod clasp configs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: simplify deploy scripts for single-script add-on model

* chore: add release.sh for versioned Marketplace deployment

* docs: update CLAUDE.md for single-script add-on deploy model

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update README for add-on deploy model and add Code Lifecycle section

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update oauth scopes for editor add-on

* feat: add onInstall handler and update onOpen signature for editor add-on

* docs: add editor add-on deploy pipeline design and plan

* fix: remove unused event params from onOpen and onInstall

* chore: set Marketplace deployment ID in release.sh

* minor husky thing

* feat: enforce main-only release and document branch workflow in README

* chore: add PR template with manual QA checklist

* chore: add CODEOWNERS

* fix: update onOpen stub example in README to match signature

* minor release doc

* docs: flag single-developer assumption in Code Lifecycle section

* updating release process

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
@aaronbrezel aaronbrezel mentioned this pull request Mar 10, 2026
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.

1 participant