Commit Graph
2 Commits
Author SHA1 Message Date
DottaandClaude Opus 5.5 411eecdccd fix(ui): store = as typed, without the highlight exporter's escapes (#19540)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People write task requests and chat messages in the Markdown editor,
and the stored text becomes the agent's prompt
> - MDXEditor supports `==highlight==` marks, and its exporter escapes
every `=` in prose to protect that syntax
> - The editor therefore stores `a=b` as `a\=b` and `x=>y` as `x\=>y`
> - A command or code snippet in a chat message then reaches the agent
as invalid code
> - This pull request removes the `=` escape when the escape cannot
change how the text parses, as #19350 did for intraword underscores
> - The benefit is that commands, assignments and arrows reach agents
exactly as the person typed them

## Linked Issues or Issue Description

Refs #19350

**What happened?**
A chat evaluation sent a `node -e` command through the chat composer.
The stored comment contained `const fs\=require(...)` and `()\=>`. Every
`=` had a backslash. The agent received invalid JavaScript, and an
exact-text check on the stored message failed.

**Expected behavior**
Text the person types is stored as typed. A `=` that cannot start or end
a highlight needs no escape.

**Steps to reproduce**
1. Type `a=b` or `x => y` in any Markdown editor field, such as the chat
composer.
2. Read the stored value through the API.
3. Before this change it is `a\=b` or `x \=> y`.

## What Changed

- New `ui/src/lib/equals-markdown.ts`: `unescapeEqualsSigns` removes
`\=` escapes from prose only when they cannot change the parse:
- any run of escaped `=` whose length is not exactly two (a highlight
sequence is exactly two `=`);
- a run of exactly two with whitespace or the text edge on both sides
(such a run can neither open nor close a highlight).
- Other two-runs, such as `a\=\=b` or `\=\=text\=\=`, stay escaped.
Escaped backslashes (`\\=`) stay as they are.
- `ui/src/lib/underscore-markdown.ts`: the code-range walk is now the
shared `rewriteMarkdownProse` helper. Code spans, code blocks and raw
HTML stay byte-identical for both rewrites. Underscore behavior does not
change.
- `MarkdownEditor.tsx`: `toStoredMarkdown` applies the new step after
the underscore step, on the exporter's exact text.
- Tests: unit cases for commands, `===`, spaced `==`, quotes and lists,
highlight-capable runs, code spans and blocks, raw HTML and escaped
backslashes. One editor test for a pasted command with a code block.

## Verification

- `npx vitest run src/components/MarkdownEditor.test.tsx src/lib
src/components/task-chat` in `ui/`: 280 files, 3388 tests passed.
- Negative control: with the new step removed from `toStoredMarkdown`,
the new editor test fails.
- Parser check: I parsed the unescaped forms with
`micromark-extension-highlight-mark` 1.2.0, the version MDXEditor uses.
`a=b and c=d`, `x === y`, `a == b == c`, `()=>x` and `x!==y` produce
zero highlight nodes. The controls `==a==`, `a ==b== c` and `say
==this== now` produce one highlight node each.
- `tsc -p ui/tsconfig.json`: no errors in the changed files. The
remaining errors are in files this change does not touch.

## Risks

- Low risk. The step only removes an escape when the highlight tokenizer
cannot use that `=`. Text the parser cannot place is returned unchanged.
- Some `==` in code-like prose (for example `x!==y`) stays escaped. This
is intentional: such a run can open a highlight.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, with tool use and code
execution in Claude Code.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 17:27:59 -06:00
DottaandClaude Opus 5.5 e9999071f3 fix(ui): store intraword underscores as typed, without the editor's escapes (#19350)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - People write task requests in the Markdown editor, and the stored
text becomes the agent's prompt
> - MDXEditor exports every literal underscore as `\_`, even inside
words such as `E2E_ASK_12` or `snake_case`
> - Agents then see backslashes the person never typed, and sometimes
repeat them in their answers
> - This pull request removes only the intraword escape when the editor
hands text back, as is already done for `\>` and `\<`
> - The benefit is that identifiers, filenames and exact strings reach
agents exactly as typed

## Linked Issues or Issue Description

**What happened?**
In QA staging smoke (launch 3108, task QA-671), a person created a task
through the UI that asked for the exact reply
`E2E_ASK_12_journey_1793913565974_507b01b8`. The stored description, and
the agent's prompt, contained `E2E\_ASK\_12\_journey\_…`. The agent's
`paperclip_finish` summary was unescaped, but its final answer copied
the escaped form. The posted reply therefore did not equal the requested
text, though the rendered Markdown looks the same. Earlier attempts
passed only when the model happened to unescape.

**Expected behavior**
Text the person typed is stored as typed. An underscore inside a word
never needs an escape, because CommonMark never treats it as emphasis.

**Steps to reproduce**
1. Type `snake_case` in any Markdown editor field, such as a new task
description or the chat composer.
2. Read the stored value through the API.
3. Before this change it is `snake\_case`.

**Paperclip version or commit**
master at da3084a05d.

## What Changed

- New `ui/src/lib/underscore-markdown.ts`:
`unescapeIntrawordUnderscores` parses the text with
`mdast-util-from-markdown`, collects the source ranges of `inlineCode`
and `code` nodes, and turns `\_` back into `_` only outside those
ranges. It only changes a run of escaped underscores between two letters
or digits (`snake\_case`, `foo\_\_bar`). Text it can't parse is left
unchanged.
- These stay exactly as the editor wrote them: code spans, and fenced or
indented code blocks at any blockquote or list depth; escapes at a word
boundary (`\_\_init\_\_`, which would otherwise render as strong
emphasis); escaped backslashes (`\\_`).
- `MarkdownEditor.tsx`: `toStoredMarkdown` applies the helper after the
existing `\>` and `\<` handling, which is unchanged.
- `ui/package.json`: `mdast-util-from-markdown` becomes a direct
dependency. It was already installed transitively at 2.0.3. Per the
lockfile policy, `pnpm-lock.yaml` is not committed.
- Tests:
- Unit cases cover lists, blockquotes, list-indented fences (including
Greptile's case), inline code, escaped and unmatched backticks,
`foo\_\_bar`, `__init__` and escaped backslashes.
- Two editor tests: the original intraword test, and a round trip of
prose plus code.
- Negative control: when the code ranges are removed, the code
byte-identity tests fail.

## Verification

- `npx vitest run src/components/task-chat src/components/MarkdownEditor
src/lib` in `ui/`: 277 files, 3314 tests passed.
- `pnpm check:token-gates`: all gates clean. `vite build` passed. No
type errors in the changed files.
- An earlier CI Build failure on this PR was `npm error notarget No
matching version found for @microsoft/teams.api@2.2.0`, in the
bundled-server prefetch. That version is published now, and the failure
is unrelated to this change.
- Not verified live on QA yet. QA only uses this after its stacks are
rolled to a build that contains it.

## Risks

- Low risk. Only escapes that cannot change rendering are removed, and
only outside parsed code.
- Stored values written before this change keep their escapes until the
text is edited again.
- Parsing adds a small cost per editor change, and only when the text
contains `\_`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), Anthropic, via Claude Code with
tool use (shell, browser, code editing). Extended reasoning was used.

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-06 06:18:47 -06:00