Skip to content

feat(doc): add --selection-with-ellipsis position flag to +media-insert#335

Merged
fangshuyu-768 merged 8 commits intomainfrom
feat/media-insert-keyword-position
Apr 20, 2026
Merged

feat(doc): add --selection-with-ellipsis position flag to +media-insert#335
fangshuyu-768 merged 8 commits intomainfrom
feat/media-insert-keyword-position

Conversation

@herbertliu
Copy link
Copy Markdown
Collaborator

@herbertliu herbertliu commented Apr 8, 2026

Summary

docs +media-insert currently only supports appending media to the end of a document. This PR adds two new flags — --selection-with-ellipsis and --before — that allow inserting an image or file at a position relative to a block identified by a text selection, using the MCP locate-doc tool (the same approach used by drive +add-comment).

This is more efficient than the earlier approach of fetching all document blocks: locate-doc returns the matching block directly without paginated enumeration.

Changes

  • shortcuts/doc/doc_media_insert.go:

    • Replace --after-keyword / --before-keyword with --selection-with-ellipsis (plain text or start...end syntax) + --before bool flag
    • Remove fetchAllBlocks, extractBlockPlainText, findInsertIndexByKeyword
    • Add locateInsertIndex(): calls MCP locate-doc, uses parent_block_id hint to walk to the root-level ancestor (zero extra API calls in the common case; falls back to single-block GET for deeper nesting, max 8 levels)
    • Update DryRun to show the MCP locate-doc step when --selection-with-ellipsis is set
    • Move MCPEndpoint call inside if hasSelection to avoid nil-config panic in tests
  • shortcuts/doc/doc_media_insert_test.go:

    • Remove tests for deleted functions (extractBlockPlainText, findInsertIndexByKeyword)
    • Add integration tests via Execute + httpmock: after-mode, before-mode, nested block (parent_block_id walking), no-match error
    • Add dry-run test: verifies locate-doc step appears when --selection-with-ellipsis is set
  • shortcuts/doc/doc_media_test.go:

    • Remove TestFetchAllBlocksPaginationViaExecute and TestDocMediaInsertDryRunWithAfterKeyword (deleted functions / renamed flags)

Test Plan

  • go test ./shortcuts/doc/... — all tests pass
  • go vet ./... — no issues
  • gofmt -l . — no unformatted files
  • locateInsertIndex coverage: 68.9% (above 60% threshold)

Related Issues

  • None

Summary by CodeRabbit

  • New Features

    • Selection-based media insertion via --selection-with-ellipsis
    • Option to insert before a selection with --before (requires selection)
    • Positioning now resolves correct insert indices within nested documents; placeholders target computed insert positions when a selection is used
    • Selection text is redacted in logs; whitespace-only selections are rejected with clearer validation
  • Tests

    • Expanded coverage for selection-driven insert flows, before/after behaviors, nested resolution, no-match errors, and dry-run plans (includes location-resolution step and computed-index placeholder)

Allows inserting images/files at a position relative to the first block
whose plain text matches a keyword (case-insensitive substring match).

- Add --after-keyword: insert after the matched root-level block
- Add --before-keyword: insert before the matched root-level block
- Flags are mutually exclusive; default behavior (append to end) unchanged
- fetchAllBlocks: paginated block listing (up to 50 pages × 200 blocks)
- extractBlockPlainText: covers text, heading1-9, bullet, ordered, todo, code, quote
- findInsertIndexByKeyword: walks parent_id chain to resolve nested blocks to their root-level ancestor
- DryRun updated to show block-listing step when keyword flag is set
- TestFetchAllBlocksPaginationViaExecute: exercises fetchAllBlocks via a
  full Execute flow with --after-keyword, covering multi-page block listing
  (fetchAllBlocks was previously at 0% coverage)
- TestDocMediaInsertDryRunWithAfterKeyword: verifies that the dry-run output
  includes a block-listing step and mentions "search blocks" in the
  description when --after-keyword is provided

fetchAllBlocks coverage: 0% → 76.2%
@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Apr 8, 2026

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds selection-aware insertion to media-insert: two flags (--selection-with-ellipsis, --before), a helper that calls MCP locate-doc and resolves a root-level insertion index by walking parent IDs, and updates dry-run and execution to create a placeholder at the computed index. extractAppendTarget returns root children.

Changes

Cohort / File(s) Summary
Selection-based insertion logic
shortcuts/doc/doc_media_insert.go
Added --selection-with-ellipsis and --before flags and validation. Implemented locateInsertIndex(...) which calls MCP locate-doc, validates matches and anchor, walks parent chain (fetching blocks as needed) to map matched block to a root-level insertion index (before/after). Updated dry-run and execution flows to use computed <locate_index> and adjusted placeholder creation. extractAppendTarget now also returns root children.
Tests & test helpers
shortcuts/doc/doc_media_insert_test.go
Updated tests for new extractAppendTarget return shape. Added helpers buildLocateDocMCPResponse, registerInsertWithSelectionStubs, assertCreateBlockIndex. Added end-to-end tests for selection-mode insert-after/insert-before, nested-anchor parent lookup, no-match error handling, and a dry-run test asserting locate-doc step and index: "<locate_index>" in plan.

Sequence Diagram

sequenceDiagram
    participant CLI as Client CLI
    participant Cmd as DocMediaInsert Command
    participant MCP as MCP (locate-doc)
    participant API as Document API

    CLI->>Cmd: run with --selection-with-ellipsis [--before]
    Cmd->>Cmd: validate flags (selection, --before requirement)
    Cmd->>MCP: POST locate-doc(selection)
    MCP-->>Cmd: return matches (blocks, anchor_block_id)
    Cmd->>API: GET /blocks/{id} while walking parent chain toward root
    API-->>Cmd: return parent/ancestor blocks
    Cmd->>Cmd: compute insertIndex (root-level index, before/after)
    Cmd->>API: POST create-block at computed insertIndex
    API-->>Cmd: respond with created block
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Suggested reviewers

  • fangshuyu-768

Poem

🐰 I sniff the selection, soft and neat,
I ask the MCP to find the beat,
I hop up parents, count each place,
Then plant a placeholder with gentle grace,
Media hops home — a perfect seat.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the primary change: adding a --selection-with-ellipsis flag to the +media-insert command for position-relative insertion.
Description check ✅ Passed The description follows the repository template with complete Summary, Changes, Test Plan, and Related Issues sections, providing comprehensive context for the feature addition.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/media-insert-keyword-position

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added domain/ccm PR touches the ccm domain size/M Single-domain feat or fix with limited business impact labels Apr 8, 2026
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 8, 2026

Greptile Summary

This PR replaces the old --after-keyword / --before-keyword flags in docs +media-insert with --selection-with-ellipsis and --before, using the MCP locate-doc tool to resolve the target block rather than paginating through all document blocks. The implementation is clean: locateInsertIndex correctly handles the parent_block_id hint for zero-extra-API-call resolution, has a visited-set for cycle detection, and the rollback path correctly captures insertIndex for both before/after modes.

Confidence Score: 5/5

Safe to merge; core logic is correct with proper cycle detection, rollback, and dry-run step accounting.

No P0 or P1 issues found. The single P2 finding (integration tests not asserting on the computed insert index) is a test-quality suggestion that does not affect production behavior. The locateInsertIndex traversal, visited-set cycle guard, and rollback all look correct.

shortcuts/doc/doc_media_insert_test.go — the create-block stub should match on the expected index to catch future regressions.

Vulnerabilities

No security concerns identified. The selection-with-ellipsis input is passed as a JSON argument to the MCP tool (not interpolated into a URL or shell command), validate.EncodePathSegment is used on all path parameters, and file path validation via validate.SafeInputPath is retained from the original implementation.

Important Files Changed

Filename Overview
shortcuts/doc/doc_media_insert.go New locateInsertIndex function and updated DryRun/Execute paths for --selection-with-ellipsis; logic is sound but integration tests don't verify the computed index value sent to the create-block API.
shortcuts/doc/doc_media_insert_test.go Good coverage of after/before/nested/no-match/dry-run paths, but the create-block stub accepts any body so index correctness isn't verified by the integration tests.

Sequence Diagram

sequenceDiagram
    participant CLI
    participant DocxAPI as Docx API
    participant MCPAPI as MCP locate-doc
    participant DriveAPI as Drive API

    CLI->>DocxAPI: GET /documents/{id}/blocks/{id} (root block)
    DocxAPI-->>CLI: root children list

    alt --selection-with-ellipsis provided
        CLI->>MCPAPI: POST /mcp tools/call locate-doc
        MCPAPI-->>CLI: {anchor_block_id, parent_block_id}
        CLI->>CLI: Walk parent chain (parent_block_id hint or GET block)
        CLI->>CLI: Resolve root-level insert index (before ? idx : idx+1)
    else default append
        CLI->>CLI: insertIndex = len(rootChildren)
    end

    CLI->>DocxAPI: POST /documents/{id}/blocks/{id}/children (index=N)
    DocxAPI-->>CLI: {block_id}

    CLI->>DriveAPI: POST /medias/upload_all (or multipart)
    DriveAPI-->>CLI: {file_token}

    CLI->>DocxAPI: PATCH /documents/{id}/blocks/batch_update
    DocxAPI-->>CLI: ok

    note over CLI,DocxAPI: On upload/bind failure: DELETE /children/batch_delete (rollback)
Loading

Reviews (4): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread shortcuts/doc/doc_media_insert.go Outdated
Comment thread shortcuts/doc/doc_media_test.go Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 8, 2026

🚀 PR Preview Install Guide

🧰 CLI update

npm i -g https://pkg.pr.new/larksuite/cli/@larksuite/cli@1e5de85f35e224d5f623305e7df0bd557882235e

🧩 Skill update

npx skills add larksuite/cli#feat/media-insert-keyword-position -y -g

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (1)
shortcuts/doc/doc_media_insert_test.go (1)

112-124: Assert the actual root-child IDs and order.

The new children return value is used to map keyword hits back to a root-level insertion index. Checking only len(children) will miss regressions that reorder or replace the IDs.

Suggested patch
 	blockID, index, children, err := extractAppendTarget(rootData, "fallback")
@@
-	if len(children) != 3 {
-		t.Fatalf("extractAppendTarget() children len = %d, want 3", len(children))
+	wantChildren := []interface{}{"c1", "c2", "c3"}
+	if !reflect.DeepEqual(children, wantChildren) {
+		t.Fatalf("extractAppendTarget() children = %#v, want %#v", children, wantChildren)
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/doc/doc_media_insert_test.go` around lines 112 - 124, The test
currently only checks len(children) which misses regressions in order or
identity; update the test around the call to extractAppendTarget(rootData,
"fallback") to assert that children equals the expected slice of root child IDs
in the correct order (e.g., compare children to the explicit expected
[]string{"child1","child2","child3"} using reflect.DeepEqual or your test
helper) and fail the test with a clear message if they differ; keep the existing
checks for blockID and index and replace the len(children) assertion with this
full equality check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/doc/doc_media_insert.go`:
- Around line 377-410: fetchAllBlocks currently can silently return a partial
result when the API still indicates more pages (has_more==true) or when
has_more==true but no page_token is provided; update fetchAllBlocks to detect
these cases and return an explicit error instead of a partial slice: inside the
loop in fetchAllBlocks, if has_more is true and page == maxPages-1 (i.e. we've
hit the maxPages limit) return an error indicating pagination truncated;
likewise if has_more is true but page_token is empty return an error indicating
inconsistent pagination (has_more without page_token); reference the
variables/functions fetchAllBlocks, maxPages, pageToken and has_more when
implementing the checks and return a descriptive error rather than breaking and
returning partial all.
- Around line 64-80: The dry-run output still shows index: "<children_len>" even
when keyword insertion is used; update the dry-run create block payload to
reflect the computed insertion index when hasKeyword is true. In the block
around afterKeyword/beforeKeyword/hasKeyword and createBlockData (built via
buildCreateBlockData) replace or override createBlockData["index"] with a
descriptive placeholder that represents the computed position (e.g.,
"<insertion_index_from_search>") when hasKeyword is true, and ensure any related
batchUpdateData or totalSteps logic (the DryRunAPI usage and totalSteps
increment) remains consistent so the dry-run plan accurately describes
keyword-mode insertion.

In `@shortcuts/doc/doc_media_test.go`:
- Around line 445-455: The test's dry-run check falsely matches document root
block fetches because it only checks for substring "/blocks" in step.URL; update
the loop in the test (the foundListBlocks logic that iterates dry.API and
inspects step.URL) to assert the exact list-all-blocks endpoint (e.g., equality
to "/blocks" or the full expected path) or alternatively check step.Description
(or another explicit field) for the "List all blocks" label to reliably detect
the extra list-all-blocks step instead of any URL containing "/blocks".
- Around line 347-405: Change the first
/open-apis/docx/v1/documents/doxcnFB/blocks stub to return "has_more": true and
only non-keyword items, then add a second GET stub for the same URL that
emulates page 2 (e.g., using a page token) containing the keyword block so
pagination must be followed; update the create-child POST stub registered for
"/open-apis/docx/v1/documents/doxcnFB/blocks/doxcnFB/children" to validate the
incoming request body (assert the "index" field or placement in the JSON matches
the computed insert index) instead of accepting any body; apply the same changes
to the analogous stubs referenced around lines 411-420 so the test actually
verifies page-token handling and that Execute inserts at the expected index.
- Around line 330-331: Before calling cmdutil.TestFactory in
TestFetchAllBlocksPaginationViaExecute, isolate the CLI config by setting the
LARKSUITE_CLI_CONFIG_DIR env var to a test temp directory; add
t.Setenv("LARKSUITE_CLI_CONFIG_DIR", t.TempDir()) at the start of
TestFetchAllBlocksPaginationViaExecute (before invoking cmdutil.TestFactory) so
the test uses an isolated config directory and won’t share state with other
tests.

---

Nitpick comments:
In `@shortcuts/doc/doc_media_insert_test.go`:
- Around line 112-124: The test currently only checks len(children) which misses
regressions in order or identity; update the test around the call to
extractAppendTarget(rootData, "fallback") to assert that children equals the
expected slice of root child IDs in the correct order (e.g., compare children to
the explicit expected []string{"child1","child2","child3"} using
reflect.DeepEqual or your test helper) and fail the test with a clear message if
they differ; keep the existing checks for blockID and index and replace the
len(children) assertion with this full equality check.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f0c5801d-351b-4e8b-8a79-34e0b5cc29ec

📥 Commits

Reviewing files that changed from the base of the PR and between f5a8fbf and 7b83ff4.

📒 Files selected for processing (3)
  • shortcuts/doc/doc_media_insert.go
  • shortcuts/doc/doc_media_insert_test.go
  • shortcuts/doc/doc_media_test.go

Comment thread shortcuts/doc/doc_media_insert.go Outdated
Comment thread shortcuts/doc/doc_media_insert.go Outdated
Comment thread shortcuts/doc/doc_media_test.go Outdated
Comment thread shortcuts/doc/doc_media_test.go Outdated
Comment thread shortcuts/doc/doc_media_test.go Outdated
Replace fetchAllBlocks + keyword scan with MCP locate-doc tool,
consistent with DriveAddComment. Flags changed from --after-keyword /
--before-keyword to --selection-with-ellipsis + --before.
@herbertliu herbertliu changed the title feat(doc): add --after-keyword/--before-keyword position flags to +media-insert feat(doc): add --selection-with-ellipsis position flag to +media-insert Apr 8, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
shortcuts/doc/doc_media_insert.go (1)

64-69: ⚠️ Potential issue | 🟡 Minor

Selection-mode dry-run still advertises append-to-end.

Once --selection-with-ellipsis is set, the plan resolves a target position, but the create-block payload still shows index: "<children_len>". That keeps the dry-run misleading for the new flow. Switch it to a target-position placeholder in selection mode.

Suggested patch
 		createBlockData := buildCreateBlockData(mediaType, 0)
 		createBlockData["index"] = "<children_len>"
+		if hasSelection {
+			createBlockData["index"] = "<target_index>"
+		}

Also applies to: 77-123

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/doc/doc_media_insert.go` around lines 64 - 69, The dry-run always
sets createBlockData["index"] to "<children_len>" even when selection mode is
active; update the logic that builds the create-block payload so when selection
:= strings.TrimSpace(runtime.Str("selection-with-ellipsis")) yields hasSelection
(selection != ""), replace the index placeholder with a target-position
placeholder (e.g. "<target-position>") instead of "<children_len>". Adjust both
the initial creation path around parentTypeForMediaType(mediaType) /
buildCreateBlockData(mediaType, 0) and the duplicate handling later in the same
function (the code covering the 77-123 region) so any createBlockData["index"]
assignment uses the selection-mode placeholder when hasSelection is true.
🧹 Nitpick comments (2)
shortcuts/doc/doc_media_insert.go (1)

435-468: Avoid hard-coding an 8-level ancestry limit here.

visited already prevents parent-chain loops. const maxDepth = 8 adds an undocumented client-side nesting cap, so a reachable match deeper than that will be reported as block_not_reachable. Prefer terminating on parent == "" / parent == cur instead of a fixed depth ceiling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/doc/doc_media_insert.go` around lines 435 - 468, Remove the
artificial const maxDepth = 8 and the depth-limited for loop; instead iterate
until you hit the root or a visited node so only the visited map prevents
infinite loops. Replace the for depth := 0; depth < maxDepth; depth++ { ... }
with an unconditional loop (for { ... }) that keeps the existing visited[cur]
check, the rootSet lookup (returning idx or idx+1), the parent hint handling via
nextParent, the runtime.CallAPI fetch using documentID and cur to get parent_id,
and the break conditions parent == "" or parent == cur; ensure no other implicit
depth cap remains.
shortcuts/doc/doc_media_insert_test.go (1)

153-205: These tests still don't prove the computed insertion position.

The new behavior lives in the MCP request args and the index sent to the create-block call, but the stubs accept any request body and the assertions only check success / presence of a locate-doc step. A regression that always appends or flips --before would still pass. Please assert the outgoing selection_with_ellipsis and create-block index for each scenario.

Also applies to: 208-321, 367-410

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/doc/doc_media_insert_test.go` around lines 153 - 205, The tests
currently register permissive stubs in registerInsertWithSelectionStubs (and the
similar stubs around the other ranges) which accept any request bodies, so they
don't verify the computed insertion position; update those stubs to assert the
outgoing MCP POST contains the expected selection_with_ellipsis value and that
the create-block POST to
"/open-apis/docx/v1/documents/{docID}/blocks/{docID}/children" includes the
correct "index" in its request body. Concretely, modify
registerInsertWithSelectionStubs (and the corresponding stub registrations in
the 208-321 and 367-410 blocks) to set an expected request body or a request
matcher on the MCP stub that checks selection_with_ellipsis equals the scenario
value, and on the create-block stub to validate the "index" field (failing the
test if mismatched); keep existing response bodies but add these request
assertions so regressions flipping/always-appending the index will fail.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@shortcuts/doc/doc_media_insert.go`:
- Around line 64-69: The dry-run always sets createBlockData["index"] to
"<children_len>" even when selection mode is active; update the logic that
builds the create-block payload so when selection :=
strings.TrimSpace(runtime.Str("selection-with-ellipsis")) yields hasSelection
(selection != ""), replace the index placeholder with a target-position
placeholder (e.g. "<target-position>") instead of "<children_len>". Adjust both
the initial creation path around parentTypeForMediaType(mediaType) /
buildCreateBlockData(mediaType, 0) and the duplicate handling later in the same
function (the code covering the 77-123 region) so any createBlockData["index"]
assignment uses the selection-mode placeholder when hasSelection is true.

---

Nitpick comments:
In `@shortcuts/doc/doc_media_insert_test.go`:
- Around line 153-205: The tests currently register permissive stubs in
registerInsertWithSelectionStubs (and the similar stubs around the other ranges)
which accept any request bodies, so they don't verify the computed insertion
position; update those stubs to assert the outgoing MCP POST contains the
expected selection_with_ellipsis value and that the create-block POST to
"/open-apis/docx/v1/documents/{docID}/blocks/{docID}/children" includes the
correct "index" in its request body. Concretely, modify
registerInsertWithSelectionStubs (and the corresponding stub registrations in
the 208-321 and 367-410 blocks) to set an expected request body or a request
matcher on the MCP stub that checks selection_with_ellipsis equals the scenario
value, and on the create-block stub to validate the "index" field (failing the
test if mismatched); keep existing response bodies but add these request
assertions so regressions flipping/always-appending the index will fail.

In `@shortcuts/doc/doc_media_insert.go`:
- Around line 435-468: Remove the artificial const maxDepth = 8 and the
depth-limited for loop; instead iterate until you hit the root or a visited node
so only the visited map prevents infinite loops. Replace the for depth := 0;
depth < maxDepth; depth++ { ... } with an unconditional loop (for { ... }) that
keeps the existing visited[cur] check, the rootSet lookup (returning idx or
idx+1), the parent hint handling via nextParent, the runtime.CallAPI fetch using
documentID and cur to get parent_id, and the break conditions parent == "" or
parent == cur; ensure no other implicit depth cap remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 433c3a8d-8d5f-45a9-9df9-90756c31b610

📥 Commits

Reviewing files that changed from the base of the PR and between 7b83ff4 and c505fdb.

📒 Files selected for processing (2)
  • shortcuts/doc/doc_media_insert.go
  • shortcuts/doc/doc_media_insert_test.go

…is set

When --selection-with-ellipsis is provided, the create-block step in dry-run
now shows index: "<locate_index>" instead of "<children_len>" to accurately
reflect that the insertion position is computed from MCP locate-doc, not
appended to end.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/doc/doc_media_insert.go`:
- Line 185: The stderr logging prints the raw variable selection (selection) via
fmt.Fprintf(runtime.IO().ErrOut, ...) which can leak document content; update
all occurrences (the fmt.Fprintf calls around "Locating block matching
selection" and the error paths around lines ~402-407 and ~474-479) to sanitize
the value before logging—e.g., replace full selection with a redacted/trimmed
representation (mask middle with "…", show only safe prefix/suffix or a length
and hash), and use that sanitized string in the fmt.Fprintf/error messages
instead of the raw selection variable.
- Around line 48-50: Add validation in the Validate function to reject blank or
whitespace-only values for the --selection-with-ellipsis flag so it cannot
silently fall back to append-mode; specifically, call
runtime.Str("selection-with-ellipsis") (RuntimeContext.Str /
RuntimeContext.Validate) and trim it (strings.TrimSpace) and return an error if
the result is empty, regardless of runtime.Bool("before") — this mirrors the
existing before-mode check around the if using runtime.Bool("before") but
enforces it unconditionally in Validate to catch cases where the flag was
provided but blank.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: db0c7312-a58c-47ae-ba53-7100d2510fae

📥 Commits

Reviewing files that changed from the base of the PR and between c505fdb and 1f63ab1.

📒 Files selected for processing (2)
  • shortcuts/doc/doc_media_insert.go
  • shortcuts/doc/doc_media_insert_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • shortcuts/doc/doc_media_insert_test.go

Comment thread shortcuts/doc/doc_media_insert.go Outdated
Comment thread shortcuts/doc/doc_media_insert.go Outdated
@herbertliu herbertliu force-pushed the feat/media-insert-keyword-position branch from 4ccb830 to 319f9fc Compare April 16, 2026 09:58
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@shortcuts/doc/doc_media_insert_test.go`:
- Around line 255-305: The test TestLocateInsertIndexNestedBlockViaExecute never
exercises the fallback ancestor-walk because the MCP response returns
parent_block_id "blk_section" which is already a direct child of the root; to
cover the deeper-ancestor branch, change the MCP response to point the anchor
under an intermediate block (e.g., parent_block_id "blk_section_child" whose
parent is "blk_section") and add a reg.Register stub for the GET
/open-apis/docx/v1/documents/{docID}/blocks/{parent_block_id} call for that
intermediate block (returning its "parent" as "blk_section"), so the code path
in the parent-walk is triggered; update the test bodies that assert
uploads/patches if needed to reflect the new nesting.
- Around line 210-250: Tests TestLocateInsertIndexAfterModeViaExecute and
TestLocateInsertIndexBeforeModeViaExecute currently only check Execute() success
but use fixtures that resolve to the same target index; update the tests so the
two modes resolve to different indices (e.g., alter the registered root children
in registerInsertWithSelectionStubs or use a fixture with an extra block like
"blk_c" so "after blk_a" != "before blk_b"), then add an assertion on the mocked
/children request body (the request sent by mountAndRunDocs/DocMediaInsert) to
verify the "index" field matches the expected value for each test; reference the
test helpers TestLocateInsertIndexAfterModeViaExecute,
TestLocateInsertIndexBeforeModeViaExecute, registerInsertWithSelectionStubs,
mountAndRunDocs and ensure the assertion inspects the request payload produced
by the code under test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 40b9ace0-5dce-498d-bd46-f8f052c013c4

📥 Commits

Reviewing files that changed from the base of the PR and between 1f63ab1 and 319f9fc.

📒 Files selected for processing (2)
  • shortcuts/doc/doc_media_insert.go
  • shortcuts/doc/doc_media_insert_test.go
✅ Files skipped from review due to trivial changes (1)
  • shortcuts/doc/doc_media_insert.go

Comment thread shortcuts/doc/doc_media_insert_test.go
Comment thread shortcuts/doc/doc_media_insert_test.go
…word-position

# Conflicts:
#	shortcuts/doc/doc_media_insert.go
#	shortcuts/doc/doc_media_insert_test.go
- Validate: reject blank/whitespace --selection-with-ellipsis unconditionally
  so a mis-typed empty value cannot silently fall back to append-mode.
- Redact the raw selection string when logging to stderr and when emitting
  error messages. --selection-with-ellipsis is copied verbatim from document
  content and may contain confidential text; the new redactSelection helper
  keeps a short prefix and rune count so operators can still identify the
  failing selection.
- Harden the after/before mode tests: root children now have three entries
  so the two modes land on different indices, and the tests decode the
  create-block request body to assert the computed `index` actually reaches
  the /children API. A regression that ignored --before would now fail.
- Harden the nested-block test so it exercises the fallback parent-walk:
  the anchor is now two levels deep (blk_grandchild under blk_section_child
  under blk_section), which forces the walk to fetch the intermediate block
  via GET /blocks/{id} to discover the root-level ancestor.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
shortcuts/doc/doc_media_insert_test.go (2)

488-492: Nit: CapturedBody == nil is always true for this GET.

The intermediate block lookup is a GET, which sends no body, so intermediateStub.CapturedBody is always nil. The combined && condition means this assertion effectively only tests CapturedHeaders == nil. Consider simplifying to intermediateStub.CapturedHeaders == nil (or whatever "was hit" signal the stub exposes) to make the intent clear.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/doc/doc_media_insert_test.go` around lines 488 - 492, The test
currently checks both intermediateStub.CapturedBody == nil and
intermediateStub.CapturedHeaders == nil, but CapturedBody is always nil for a
GET; change the assertion to only check the stub's actual "was hit" signal (e.g.
intermediateStub.CapturedHeaders == nil) or the stub's explicit hit flag if
available (e.g. intermediateStub.WasHit) so the assertion clearly tests that the
GET /blocks/blk_section_child stub was invoked; update the failing t.Errorf
message accordingly to reflect that the header/hit check failed.

587-596: Dry-run index assertion passes silently if the key is missing.

The index check only fires when step.Body["index"] is present. A regression that drops the placeholder altogether (or renames the field) would leave ok == false and the test would pass. Consider failing when the create-block step is found but has no index key, so the contract "<locate_index> is the placeholder in selection mode" is actually enforced.

Proposed tightening
-	for _, step := range dry.API {
-		if strings.Contains(step.URL, "/children") && step.Body != nil {
-			if idx, ok := step.Body["index"]; ok {
-				if idx != "<locate_index>" {
-					t.Fatalf("create-block index in selection mode = %q, want <locate_index>", idx)
-				}
-			}
-		}
-	}
+	foundCreate := false
+	for _, step := range dry.API {
+		if strings.Contains(step.URL, "/children") && step.Body != nil {
+			foundCreate = true
+			idx, ok := step.Body["index"]
+			if !ok {
+				t.Fatalf("create-block step missing 'index' field in selection mode: %+v", step.Body)
+			}
+			if idx != "<locate_index>" {
+				t.Fatalf("create-block index in selection mode = %q, want <locate_index>", idx)
+			}
+		}
+	}
+	if !foundCreate {
+		t.Fatalf("dry-run should include a create-block step, got: %+v", dry.API)
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@shortcuts/doc/doc_media_insert_test.go` around lines 587 - 596, The test
currently only asserts the value of step.Body["index"] when the key exists,
which allows a missing key to pass; update the loop over dry.API in
shortcuts/doc/doc_media_insert_test.go so that for each step where
strings.Contains(step.URL, "/children") && step.Body != nil you assert that the
"index" key exists (ok == true) and then assert its value equals
"<locate_index>" — if the key is missing or the value differs call t.Fatalf with
a clear message referencing the step and the expected "<locate_index>"
placeholder; locate the check around the for _, step := range dry.API loop and
replace the nested ifs to first require presence of "index" and then validate
its value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@shortcuts/doc/doc_media_insert_test.go`:
- Around line 488-492: The test currently checks both
intermediateStub.CapturedBody == nil and intermediateStub.CapturedHeaders ==
nil, but CapturedBody is always nil for a GET; change the assertion to only
check the stub's actual "was hit" signal (e.g. intermediateStub.CapturedHeaders
== nil) or the stub's explicit hit flag if available (e.g.
intermediateStub.WasHit) so the assertion clearly tests that the GET
/blocks/blk_section_child stub was invoked; update the failing t.Errorf message
accordingly to reflect that the header/hit check failed.
- Around line 587-596: The test currently only asserts the value of
step.Body["index"] when the key exists, which allows a missing key to pass;
update the loop over dry.API in shortcuts/doc/doc_media_insert_test.go so that
for each step where strings.Contains(step.URL, "/children") && step.Body != nil
you assert that the "index" key exists (ok == true) and then assert its value
equals "<locate_index>" — if the key is missing or the value differs call
t.Fatalf with a clear message referencing the step and the expected
"<locate_index>" placeholder; locate the check around the for _, step := range
dry.API loop and replace the nested ifs to first require presence of "index" and
then validate its value.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4842a267-7b53-4ef5-888e-61acd257c832

📥 Commits

Reviewing files that changed from the base of the PR and between 319f9fc and 6e667ee.

📒 Files selected for processing (2)
  • shortcuts/doc/doc_media_insert.go
  • shortcuts/doc/doc_media_insert_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • shortcuts/doc/doc_media_insert.go

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 20, 2026

Codecov Report

❌ Patch coverage is 84.61538% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 60.64%. Comparing base (1262aac) to head (1e5de85).
⚠️ Report is 8 commits behind head on main.

Files with missing lines Patch % Lines
shortcuts/doc/doc_media_insert.go 84.61% 13 Missing and 9 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #335      +/-   ##
==========================================
+ Coverage   60.19%   60.64%   +0.44%     
==========================================
  Files         390      393       +3     
  Lines       33433    33789     +356     
==========================================
+ Hits        20125    20490     +365     
+ Misses      11426    11391      -35     
- Partials     1882     1908      +26     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

fangshuyu-768 added a commit that referenced this pull request Apr 20, 2026
Follow-up to #335 review: closes a handful of UX and robustness gaps in
the new --selection-with-ellipsis flow.

- Flag description rewritten to make the "insert at the top-level
  ancestor" semantics explicit — when the selection is inside a callout,
  table cell, or nested list, media lands outside that container, not
  inside. Also calls out the 'start...end' disambiguator.

- locate-doc is now called with limit=2 so an ambiguous selection
  (same phrase in more than one block) surfaces a stderr warning
  pointing at 'start...end', instead of silently picking the first
  match. The first-match return behaviour is unchanged.

- When the anchor is nested below the root, locateInsertIndex now
  logs a note to stderr naming the walk depth and the root-level
  ancestor's insert index. Users don't have to guess why the image
  landed outside the callout they were editing.

- maxDepth bumped 8 → 32 with a comment explaining the invariants:
  `visited` is the real cycle guard, `maxDepth` is belt-and-suspenders.
  32 comfortably exceeds real docx nesting depth so a deeply-nested
  but well-formed anchor is no longer silently rejected.

- Comment added before the parent-walk loop noting why the API calls
  are serial (each level's parent_id is only known after the previous
  GET returns; can't be batched or parallelised).

Tests:

- TestLocateInsertIndexWarnsOnMultipleMatches: stubs two matches,
  asserts the stderr warning names the ambiguity and mentions
  'start...end', and that the first-match insert index is unchanged.
- TestLocateInsertIndexLogsNestedAnchor: anchor two levels below root,
  asserts stderr carries the "nested … top-level ancestor" note.
- TestLocateInsertIndexCycleDetection: malformed parent chain with
  blk_x.parent = blk_y and blk_y.parent = blk_x, neither reachable
  from root. Registering a single GET /blocks/blk_y stub also bounds
  the call count — a regression that broke `visited` tracking would
  either hang or fail via httpmock's extra-call guard.
Follow-up to #335 review: closes a handful of UX and robustness gaps in
the new --selection-with-ellipsis flow.

- Flag description rewritten to make the "insert at the top-level
  ancestor" semantics explicit — when the selection is inside a callout,
  table cell, or nested list, media lands outside that container, not
  inside. Also calls out the 'start...end' disambiguator.

- locate-doc is now called with limit=2 so an ambiguous selection
  (same phrase in more than one block) surfaces a stderr warning
  pointing at 'start...end', instead of silently picking the first
  match. The first-match return behaviour is unchanged.

- When the anchor is nested below the root, locateInsertIndex now
  logs a note to stderr naming the walk depth and the root-level
  ancestor's insert index. Users don't have to guess why the image
  landed outside the callout they were editing.

- maxDepth bumped 8 → 32 with a comment explaining the invariants:
  `visited` is the real cycle guard, `maxDepth` is belt-and-suspenders.
  32 comfortably exceeds real docx nesting depth so a deeply-nested
  but well-formed anchor is no longer silently rejected.

- Comment added before the parent-walk loop noting why the API calls
  are serial (each level's parent_id is only known after the previous
  GET returns; can't be batched or parallelised).

Tests:

- TestLocateInsertIndexWarnsOnMultipleMatches: stubs two matches,
  asserts the stderr warning names the ambiguity and mentions
  'start...end', and that the first-match insert index is unchanged.
- TestLocateInsertIndexLogsNestedAnchor: anchor two levels below root,
  asserts stderr carries the "nested … top-level ancestor" note.
- TestLocateInsertIndexCycleDetection: malformed parent chain with
  blk_x.parent = blk_y and blk_y.parent = blk_x, neither reachable
  from root. Registering a single GET /blocks/blk_y stub also bounds
  the call count — a regression that broke `visited` tracking would
  either hang or fail via httpmock's extra-call guard.

Co-authored-by: fangshuyu-768 <shuyufang768@outlook.com>
@fangshuyu-768 fangshuyu-768 merged commit 9057299 into main Apr 20, 2026
20 checks passed
@fangshuyu-768 fangshuyu-768 deleted the feat/media-insert-keyword-position branch April 20, 2026 15:24
@liangshuo-1 liangshuo-1 mentioned this pull request Apr 21, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

domain/ccm PR touches the ccm domain size/M Single-domain feat or fix with limited business impact

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants