Skip to content

Add "My Words" collaborative editor (v1, standalone)#499

Open
kcarnold wants to merge 11 commits into
mainfrom
claude/confident-ride-7s1c3m
Open

Add "My Words" collaborative editor (v1, standalone)#499
kcarnold wants to merge 11 commits into
mainfrom
claude/confident-ride-7s1c3m

Conversation

@kcarnold

Copy link
Copy Markdown
Contributor

A new sidebar page where the AI can edit the document but may never
originate words. Every word it places must be lifted from the writer's
own corpus (document + scratchpad + their chat messages), joined only by
punctuation and a small closed set of glue words.

  • corpus.ts: pure, unit-tested phrase-level validator (buildCorpus +
    validateText + GLUE_WORDS). Lifted spans must appear verbatim in the
    corpus; new content-word adjacencies are illegal unless glue-bridged.
  • my-words page: AI tool loop (view / str_replace / insert / highlight)
    via the ai SDK. Inserted text is validated against a freshly assembled
    corpus before being applied; rejections are fed back to the model so it
    retries or asks the writer. AI speech shows as an ephemeral caption with
    no scrollback; the scratchpad takes most of the height. The model gets
    lightweight activity signals rather than a full-document dump.
  • EditorAPI gains getDocText + applyEdit, implemented over Lexical in the
    standalone editor; Word/Google Docs get getDocText plus typed applyEdit
    TODO stubs so the same page can drive those hosts later.

Wired into the existing page nav (pageContext, navbar, app).

Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U

A new sidebar page where the AI can edit the document but may never
originate words. Every word it places must be lifted from the writer's
own corpus (document + scratchpad + their chat messages), joined only by
punctuation and a small closed set of glue words.

- corpus.ts: pure, unit-tested phrase-level validator (buildCorpus +
  validateText + GLUE_WORDS). Lifted spans must appear verbatim in the
  corpus; new content-word adjacencies are illegal unless glue-bridged.
- my-words page: AI tool loop (view / str_replace / insert / highlight)
  via the ai SDK. Inserted text is validated against a freshly assembled
  corpus before being applied; rejections are fed back to the model so it
  retries or asks the writer. AI speech shows as an ephemeral caption with
  no scrollback; the scratchpad takes most of the height. The model gets
  lightweight activity signals rather than a full-document dump.
- EditorAPI gains getDocText + applyEdit, implemented over Lexical in the
  standalone editor; Word/Google Docs get getDocText plus typed applyEdit
  TODO stubs so the same page can drive those hosts later.

Wired into the existing page nav (pageContext, navbar, app).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U
claude and others added 10 commits June 25, 2026 18:03
The Word task pane already renders the shared App + navbar with
wordEditorAPI, so the "My Words" tab appears there once edits work.
Implement str_replace and insert via Office.js body.search +
range.insertText. Edits honor the document's Track Changes mode
automatically, so they appear as accept/reject revisions when the
user has Track Changes on.

Limitation: Word's search is ~255 chars and does not cross paragraph
breaks, so this covers sentence/phrase-level edits (how the AI works),
not multi-paragraph spans.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U
The `view` tool now prefixes each paragraph with its 1-based number, and
`insert` accepts a `paragraph` number + `position` to place a new
paragraph relative to it. This shared paragraph coordinate system is more
robust than text anchoring — and on Word it sidesteps the ~255-char,
single-paragraph search limit entirely (uses body.paragraphs +
insertParagraph instead of search).

- EditorAPI gains getParagraphs(); DocEdit's insert variant gains
  paragraph/position.
- Implemented over Lexical (standalone), Word (body.paragraphs), and a
  split-based getParagraphs for Google Docs.
- System prompt prefers paragraph-targeted inserts and short edits.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U
- Tool input schemas use zod (added as a direct dependency) instead of
  hand-written jsonSchema, for more robust tool-call argument handling.
- str_replace / insert now return a lightweight description of the result:
  the new paragraph count plus a clipped 3-paragraph window around the
  edit, so the model can track paragraph-number shifts between steps.
- str_replace is constrained to a short, single-paragraph span (in both
  the description and the failure message), since long cross-paragraph
  old_str cannot be matched in Word and is brittle elsewhere.
- System prompt reframed: the AI is a non-directive writing tutor that
  leads with questions and reflection, while its edits only ever rearrange
  the writer's own words.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U
Past user messages and the scratchpad are part of the corpus, but the
model could easily treat chat turns as instructions rather than a word
bank. Clarify on three fronts:

- System prompt: frame the document + scratchpad + chat messages as one
  word bank to quote from freely, and state explicitly that the writer's
  messages are a source of words, not just instructions.
- REJECTED tool result: remind the model it can lift from the document,
  the scratchpad, or anything the writer has said.
- `view` now also returns the scratchpad (clearly labeled, unnumbered),
  so the model can see the full corpus on demand instead of relying only
  on delta activity notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U
The activity note previously pushed the full scratchpad into the
transcript on every change — redundant with the scratchpad now shown by
`view`, and it accumulated a fresh full copy per edit over a session.

Make the note a lightweight flag ("scratchpad changed — call view") and
let `view` be the single source of truth for current scratchpad content.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U
A `view` result (or any tool result) is a message that persists in the
transcript and is re-sent every subsequent turn — so pulling state via
`view` is not lighter than pushing it; both land in context, and frequent
views accumulate many stale full-document/scratchpad snapshots.

Since the document is the source of truth in the editor, the transcript
does not need to remember past states. Persist only the conversation (the
writer's turns + the assistant's captions) and drop each turn's
intermediate tool calls/results. The model re-reads current state with
`view` within a turn when it needs it, and that dump is discarded
afterward instead of piling up.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XtzcT6Yo9dVoCHsWgNMQ6U
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