feat(docs): add guided suggestion workflow - #18
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
Replacement correctness and concurrency issues, plus empty-search validation, remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds a guided gws docs +suggest workflow for creating and managing Google Docs suggestions.
Changes:
- Adds insert, replace, delete, list, accept, and reject actions.
- Reuses structured document reading and preview-field handling.
- Documents the workflow and adds a minor changeset.
Review findings:
- Critical (2 votes): Replacement lacks optimistic concurrency protection via
requiredRevisionId. - Moderate (3 votes): Replacement ignores
--tab-idwhen locating matches. - Moderate (1 vote): Empty
--findvalues are not rejected. - Nit (1 vote): Several helper actions duplicate raw API wrapper behavior.
- Nit (1 vote): New workflow branches lack stub-server coverage.
File summaries
| File | Description |
|---|---|
README.md |
Documents the suggestion workflow. |
crates/google-workspace-cli/src/helpers/docs/suggest.rs |
Implements suggestion commands and tests. |
crates/google-workspace-cli/src/helpers/docs/read.rs |
Enables shared document reading. |
crates/google-workspace-cli/src/helpers/docs.rs |
Registers and dispatches the helper. |
.changeset/docs-suggest-workflow.md |
Adds release metadata. |
Review details
Suppressed comments (5)
crates/google-workspace-cli/src/helpers/docs/suggest.rs:318
- This only records the first occurrence in each text run. For a run such as
foo foo,find_unique_text_runreports one match and the helper replaces the first occurrence even though--findis not unique. Iterate all occurrences so the uniqueness check can reject repeated matches before constructing the suggestion.
let tab = object
crates/google-workspace-cli/src/helpers/docs/suggest.rs:328
+readnormalizes headers, footers, and footnotes too, and this traversal visits them, but it carries onlytabId. A match in one of those segments is later emitted as a body range because the replacement request has no segment ID, so it can fail or target the wrong segment. Propagate the segment ID or reject non-body matches explicitly.
object.get("endIndex").and_then(Value::as_i64),
) {
crates/google-workspace-cli/src/helpers/docs/suggest.rs:245
- An empty
--findvalue passes validation and produces a zero-lengthdeleteContentRange(startIndex == endIndex), which the Docs API rejects. Reject an empty search string before reading and building the batch request.
.into(),
crates/google-workspace-cli/src/helpers/docs/suggest.rs:23
- Aside from the multi-step
replacepath, these new write/action subcommands mostly serialize a few flags into onedocuments.batchUpdatecall. That is the single-API-wrapper anti-pattern explicitly rejected bycrates/google-workspace-cli/src/helpers/README.md:24-28and its litmus test at:20; the raw command already supports the same requests with--allow-unknown-fields. Please narrow the helper to workflow/orchestration that Discovery cannot provide, or document and justify an explicit exception.
let mut cmd = Command::new("+suggest").about("[Helper] Create and manage Docs suggestions");
cmd = cmd.subcommand(
Command::new("insert")
.about("Insert text as a suggestion")
.arg(document())
crates/google-workspace-cli/src/helpers/docs/suggest.rs:133
- The new tests cover only pure JSON builders;
handle_list, the read-then-writereplaceflow, andexecute_suggestion_writeare not exercised. That leaves API method lookup, global--dry-runpropagation, authentication scope selection, and the actual request/response path uncovered. Add stub-server tests for the new workflow branches, following the existing local-server pattern incrates/google-workspace-cli/src/helpers/docs/read_tests.rs.
)))
- Files reviewed: 5/5 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Addressed the Copilot review findings in commit
Validation: 746 unit tests, 22 integration tests, 4 file-root tests, rustfmt, Clippy, and a live Google Docs replacement smoke test. Required CI checks are running on the new commit. |
There was a problem hiding this comment.
🟡 Changes recommended
Moderate findings remain around child-tab replacement, overlapping match detection, and OAuth scope selection.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (4)
Previously missed (2) — in code that hasn't changed since the last review.
crates/google-workspace-cli/src/helpers/docs/suggest.rs:368
str::match_indicesreports non-overlapping matches, so--find 'aa'against a text run containingaaarecords only the match at offset 0 even though the input is ambiguous. The helper then replaces the first occurrence instead of rejecting multiple matches; enumerate candidate character boundaries and teststarts_with(needle)so overlapping matches are counted.
crates/google-workspace-cli/src/helpers/docs/suggest.rs:113- The new helper's tests exercise private body-building functions, but no test covers registering
+suggestinDocsHelper, required argument failures, or the handler/executor path (including the advertised credential-free dry run). The helper checklist requires command registration, required-argument, and happy-path coverage, so a wiring regression could pass the current suite; add focused CLI/handler tests.
crates/google-workspace-cli/src/helpers/docs/suggest.rs:164
RestMethod.scopesis an alternatives list, not a set;main.rs:338-346explicitly selects only the first scope because requesting all alternatives can apply restrictive scopes and fail. Passing every Docs scope to OAuth here can therefore request incompatible/overbroad permissions and break suggestion writes. Reusecrate::select_scope(&method.scopes)when acquiring the token.
let scopes: Vec<&str> = method.scopes.iter().map(String::as_str).collect();
crates/google-workspace-cli/src/helpers/docs/suggest.rs:25
- This adds
insertas a wrapper around onedocuments.batchUpdatecall, and the same pattern is repeated fordelete-textand the accept/reject/delete actions below. The helper rules inAGENTS.mdprohibit helpers that merely wrap a single Discovery operation; keep the orchestration that adds value (for example,replace/list) and expose the one-call operations through the raw command instead.
cmd = cmd.subcommand(
Command::new("insert")
.about("Insert text as a suggestion")
.arg(document())
.arg(text_arg())
.arg(Arg::new("tab-id").long("tab-id").value_name("ID")),
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
|
Addressed the newest Copilot review findings in commit
Validation: 748 unit tests, 22 integration tests, 4 file-root tests, rustfmt, Clippy, and diff checks pass. |
Summary
gws docs +suggestfor insert, exact replacement, range deletion, listing, and suggestion lifecycle actionsVerification
cargo build --workspace --lockedcargo test -p google-workspace-cli --quiet(743 unit, 22 integration, 4 file-root tests)cargo clippy -p google-workspace-cli -- -D warnings