Skip to content

feat(docs): add guided suggestion workflow - #18

Merged
ratovarius merged 5 commits into
developfrom
feat/docs-suggest-workflow
Sep 18, 2026
Merged

ratovarius merged 5 commits into
developfrom
feat/docs-suggest-workflow

Conversation

@ratovarius

Copy link
Copy Markdown
Owner

Summary

  • add gws docs +suggest for insert, exact replacement, range deletion, listing, and suggestion lifecycle actions
  • hide the preview-only unknown-field workaround inside the helper
  • document the workflow and add a minor changeset

Verification

  • cargo build --workspace --locked
  • cargo test -p google-workspace-cli --quiet (743 unit, 22 integration, 4 file-root tests)
  • cargo clippy -p google-workspace-cli -- -D warnings
  • live Google Docs smoke test: insert, list, accept, reject, delete, exact replace, and suggested deletion
  • temporary live test document was deleted afterward

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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-id when locating matches.
  • Moderate (1 vote): Empty --find values 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_run reports one match and the helper replaces the first occurrence even though --find is 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

  • +read normalizes headers, footers, and footnotes too, and this traversal visits them, but it carries only tabId. 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 --find value passes validation and produces a zero-length deleteContentRange (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 replace path, these new write/action subcommands mostly serialize a few flags into one documents.batchUpdate call. That is the single-API-wrapper anti-pattern explicitly rejected by crates/google-workspace-cli/src/helpers/README.md:24-28 and 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-write replace flow, and execute_suggestion_write are not exercised. That leaves API method lookup, global --dry-run propagation, 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 in crates/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.

Comment thread crates/google-workspace-cli/src/helpers/docs/suggest.rs Outdated
Comment thread crates/google-workspace-cli/src/helpers/docs/suggest.rs Outdated
@ratovarius ratovarius self-assigned this Sep 18, 2026
@ratovarius

Copy link
Copy Markdown
Owner Author

Addressed the Copilot review findings in commit 9ea63db:

  • replacement now requires and sends requiredRevisionId from the read response
  • --tab-id is honored during replacement lookup
  • empty --find is rejected
  • repeated occurrences within a text run are rejected
  • replacement lookup is limited to document body blocks, avoiding header/footer/footnote mis-targeting

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 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_indices reports non-overlapping matches, so --find 'aa' against a text run containing aaa records 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 test starts_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 +suggest in DocsHelper, 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.scopes is an alternatives list, not a set; main.rs:338-346 explicitly 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. Reuse crate::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 insert as a wrapper around one documents.batchUpdate call, and the same pattern is repeated for delete-text and the accept/reject/delete actions below. The helper rules in AGENTS.md prohibit 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

Comment thread crates/google-workspace-cli/src/helpers/docs/suggest.rs
@ratovarius

Copy link
Copy Markdown
Owner Author

Addressed the newest Copilot review findings in commit 19a9155:

  • recurse through nested childTabs during replacement lookup
  • detect overlapping matches such as aa in aaa
  • use the repository select_scope helper instead of requesting every alternative OAuth scope
  • added regression tests for child tabs and overlapping matches

Validation: 748 unit tests, 22 integration tests, 4 file-root tests, rustfmt, Clippy, and diff checks pass.

@ratovarius
ratovarius merged commit c0ffd66 into develop Sep 18, 2026
4 checks passed
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.

2 participants