Skip to content

ChangeDown editing surface: multi-batch CriticMarkup review pass has a grain mismatch #1

Description

@hackerbara

Summary

A review pass (compose N anchored comments on a doc, leave it resolvable thread-by-thread) is one cognitive unit for the reviewer, but the propose_change tool decomposes it into N transactions each with its own freshness math. The cost of that decomposition is paid in re-reads, coordinate bookkeeping, and retries. The tool is correct — every op did what its contract says — but the grain of the tool and the grain of the work disagree.

Surfaced during a 14-comment review pass on docs/superpowers/specs/2026-05-27-word-body-led-actionability-design.md. Edits landed cleanly but took 3 batches + re-reads to get there when it should have been ~1.

Full reviewer's-eye writeup: docs/findings/2026-05-27-multi-batch-criticmarkup-edit-friction.md

Friction points

  1. In-batch coordinate shift can't be precomputed. "Offsets auto-adjusted between ops" is the right contract, but op 2's coordinate isn't the one I read — it's post-op-1. I can't precompute all coordinates from one fresh read. Op index 4 in a batch failed even though re-reading showed the content at the cited line.
  2. All-or-nothing rollback inverts the risk math at scale. For a 14-op batch, even a 5% per-op fail rate → ~51% batch fail rate, and every retry re-pays the cost of ops that already succeeded. Rational risk management pushes toward 1-op batches, which destroys the atomicity benefit.
  3. Cross-batch coordinate freshness loss from my own edits. Header expansion + appended footnote definitions shifted numbering; required a re-read between every batch.
  4. Content-anchor vs LINE:HASH split. The content anchor "survives slight shifts" but the schema requires at: LINE:HASH. Most review comments don't conceptually need exact line addressing — they need "find this phrase, attach a comment."
  5. Anchor copy precision (backticks, smart quotes, em-dashes, footnote markers). Self-inflicted but systematic: the rendering layer and the matcher disagree by design.
  6. Working-memory load ~5x the actual review complexity; the gradient pulled toward quantity-over-quality.

What works well (keep)

  • Response shape (affected_lines.lines w/ current hashes, created_regions, document_state) — excellent for re-orientation
  • Specific, honest error messages
  • Multi-form normalization matching (NFKC, whitespace-collapsed, view-surface, decided-text, current-text)
  • Auto-supersession on re-propose

Proposals (reviewer perspective; impl cost not assessed)

  • partial: true mode for propose_change returning applied[] + failed[] without rollback. Atomicity stays default; opt out to salvage partial success.
  • Content-anchor-primary addressingLINE:? / anchor: syntax that searches by content with line as hint.
  • "Did you mean?" candidate in match-failure errors — highest-similarity substring on the cited line. Catches backtick/quote/dash misses in one round trip.
  • Optional review-group identityreview_group: tying multiple propose_change calls into one author-visible review pass (vs. emergent cn-1/cn-2/cn-3).
  • Surface in-batch coordinate-shift math in failure errors ("op N shifted by K lines due to prior op M; content was at line X").

The real design question

What should "a review pass" be as a first-class object in the tool, rather than an emergent property of many propose calls? That's the conversation worth having alongside design refinement.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions