feat(word-addin): edit-protocol groundwork - occurrence parsing, honest skip statuses, location-hint slot
WHY THIS MATTERS
When a user asks Mike to replace a word that appears several times in the
document, the add-in used to dead-end: the model had no way to say "all
occurrences", and every skip collapsed into one misleading card message
("source text was not found" - even when the text WAS found, more than
once, or was simply too long to search for). This commit lays the client
groundwork for multi-occurrence editing without changing any behavior yet:
every piece here is inert until the engine commit wires it up.
WHAT IS THE <occurrence> TAG
The edit transport protocol (<original>/<replacement>/<reason> blocks
streamed inside the model's answer) gains one optional tag:
<original>Acme Corp</original>
<replacement>Acme Ltd</replacement>
<occurrence>all</occurrence>
<reason>Rename the company everywhere.</reason>
"all" is the ONLY value. There is deliberately no numeric form
(<occurrence>3</occurrence>) - three independent reasons, each fatal:
1. Language models miscount occurrences.
2. The index space is unstable mid-stream: a tracked deletion keeps the
deleted text searchable, and a replacement that CONTAINS the original
("Supplier" → "Supplier Inc.") grows the match count as edits apply.
3. Word's search silently drops overlapping matches (office-js#4992), so
"3rd occurrence in the document" and search result items[2] can
legitimately disagree.
An index failure would be silent and land a redline on the wrong sentence;
"all" needs no counting at all. Unknown values fail closed to the
historical single-occurrence contract.
HOW IT WORKS
- redline.ts parses the tag in BOTH parsers - the sealed-block regex
(COMPLETE_TAGGED_EDIT) and the streaming provisional parser - because a
tag only one of them knows would either never seal a block or leak raw
markup into the visible card mid-stream. blockIndex assignment is
byte-identical for tag-free content: it feeds the digest that names each
edit's hidden Word bookmark, so changing it would orphan every
historical edit's anchor.
- wordChatTypes.ts adds two card statuses ("unsearchable", "conflicted")
so the three previously-conflated skip reasons can each say something
true, plus locationHint/appliedMatches runtime fields.
- EditCard.tsx maps the new statuses to accurate copy, upgrades the
ambiguous message to name the match count and the way out ("Tell Mike
which one to change."), and reports multi-place applies ("Applied to
the document in N places.").
- EditCardUI.tsx (the design-system card shared with the web app) gains an
optional locationHint line - "In: "paragraph snippet"" - so a user can
catch an edit that landed in the wrong place BEFORE accepting it.
- wordEditAnchors.ts adds listWordEditAnchorIds(prefix): a replace-all
edit persists one anchor per applied occurrence under `${key}#${pass}`,
and a reloaded task pane discovers how many passes existed by
enumerating the registry.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A5yxFV2YCFmR61KLyB33yi
| Repository | open-legal-products/mike |
|---|---|
| Author | Amal <mamalanand3@gmail.com> |
| Authored | |
| Committed | |
| Parents | 01867ebd |
| Stats | 5 files changed , +127 , -4 |
| Part of | Make Word edit approval reliable across hosts |
Capture this commit into my fork
Download a Markdown prompt that tells Claude how to port this
exact commit into your working tree. Run it via
claude -p < capture-commit-30758b55.md
from inside the repo you want the change in.