Files
PaperClipAI/ui
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
..
…

@paperclipai/ui

Published static assets for the Paperclip board UI.

What gets published

The npm package contains the production build under dist/. It does not ship the UI source tree or workspace-only dependencies.

Storybook

Storybook config, stories, and fixtures live under ui/storybook/.

pnpm --filter @paperclipai/ui storybook
pnpm --filter @paperclipai/ui build-storybook

Typical use

Install the package, then serve or copy the built files from node_modules/@paperclipai/ui/dist.

Editor dependency identity

Keep the root and workspace overrides for @codemirror/state, @codemirror/view, and @lezer/common aligned. CodeMirror requires shared extension identity, while Lezer parsers and syntax highlighters require shared NodeProp IDs. Multiple Lezer copies can crash code-block highlighting with tags is not iterable. src/lib/codemirror-single-instance.test.ts checks the installed dependency graph and highlights sample code through the editor's real language dependencies. GitHub Actions owns regeneration of pnpm-lock.yaml.