Files
PaperClipAI/ui
DirkandClaude Opus 4.8 8b1483e601 fix(ui): render board approval payload prose as markdown (#9817)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work
> - The approval queue is how agents surface decisions that need a
human, so the approval card is often the only thing an operator reads
before approving or rejecting
> - Agents author those payloads in markdown, because markdown is what
they produce everywhere else in the product
> - `ApprovalPayload.tsx` renders the four prose fields of a board
approval as bare text nodes, while `CommentThread` on the same page
renders through `MarkdownBody` — same authors, two different renderers
> - So the operator sees literal `##`, `**bold**`, backticks and
`[link](url)` in the payload, and correctly formatted text in the
comments directly below it
> - This pull request routes those four fields through the existing
`MarkdownBody` component
> - The benefit is that the highest-stakes text in the product becomes
readable, with no new dependency and no schema change

## Linked Issues or Issue Description

Refs #4911 — prior art, see the note at the bottom of this description.

No open issue covers this, so per (B) here is the bug report:

**What happened:** On a board approval, the `summary`,
`recommendedAction`, `nextActionOnApproval` and `risks` fields display
raw markdown source. Headings appear as literal `##` mid-paragraph,
inline code keeps its backticks, links show as `[text](url)`, and both
levels of a nested bullet list collapse into one run-on paragraph.

**What was expected:** The same rendering the comment thread further
down the same page already gives, since both are agent-authored
markdown.

**Steps to reproduce:** Open any `request_board_approval` whose
`summary` contains markdown — headings, a nested list, code spans or
links.

**Where:** `ui/src/components/ApprovalPayload.tsx`,
`BoardApprovalPayloadContent`.

## What Changed

- `ui/src/components/ApprovalPayload.tsx`: import `MarkdownBody` and
render `summary`, `recommendedAction`, `nextActionOnApproval` and each
`risks` entry through it instead of `<p>` / `<span>` text nodes.
`MarkdownBody` defaults `softBreaks` to `true`, which is the same
behaviour `CommentThread` opts into explicitly, so paragraph handling
matches the comments.
- `stripLeadingListMarker`: risks already render inside a custom bullet
row, so an authored leading `-` / `*` / `•` would nest a second bullet
inside the first. One leading marker is stripped per entry.
- The risk bullet dot gains `shrink-0` so it keeps its shape next to
block-level markdown content.
- `title` stays plain text — it is a one-liner and markdown there is
noise.
- `proposedComment` stays a verbatim `<pre>` block — it is draft text
intended to be posted elsewhere, so it must not be reinterpreted.
- `ui/src/components/ApprovalPayload.test.tsx`: tests for markdown
rendering in all four fields, the leading-list-marker strip, and that
`title` and `proposedComment` remain verbatim.

## Verification

- `npx vitest run ui/src/components/ApprovalPayload.test.tsx` — 5
passed.
- `npx vitest run ui/src/components/ApprovalPayload.test.tsx
ui/src/components/CommentThread.test.tsx` — 12 passed, confirming the
shared `MarkdownBody` path is not disturbed.
- Manual, measured rather than eyeballed: I ran a patched build in a
throwaway container beside an unpatched one and pointed both at the same
real approval payload, then counted nodes in the rendered DOM.

  | | unpatched | patched |
  |---|---|---|
  | `.paperclip-markdown` nodes | 0 | 3 |
  | raw backticks in visible text | yes | no |
  | rendered `h2` | 0 | 5 |
  | rendered `li` | 0 | 16 |

## Risks

Low, and confined to the board approval card.

- Rendering scope widens from text to markdown on four fields. A payload
that contains markdown-significant punctuation but was authored as prose
could render differently than before. This is the intended change, and
it matches how the same author's text is already rendered in comments on
the same page.
- `stripLeadingListMarker` removes one leading list marker per risk
entry. A risk that genuinely begins with a literal hyphen followed by a
space loses that hyphen. Chosen over the alternative of a visible double
bullet on the common case.
- No schema change, no migration, no new dependency. `MarkdownBody` is
already used elsewhere in the same directory.

## Model Used

Claude Opus 4.8 (`claude-opus-4-8`), via Claude Code, with extended
thinking and tool use (repository search, file editing, local test
execution, and headless-browser DOM measurement of the before/after
renders).

## 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 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
- [ ] I have updated relevant documentation to reflect my changes — no
docs describe this rendering behaviour, so there was nothing to update
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green — pending first CI run on this PR
- [ ] Greptile is 5/5 with no open P2s, recommendations, or follow-ups —
pending first review
- [x] I will address all Greptile and reviewer comments before
requesting merge

---

### On the prior PR

@alxhrzg opened #4911 for this same bug first, and reached the same
conclusion I did: route the four fields through `MarkdownBody`. Credit
for spotting it and for the diagnosis goes there.

That PR has been conflicting against base and untouched since May.
Rather than let the fix sit, this PR reapplies the idea on current
`master` and adds what #4911 was missing: test coverage, the
nested-bullet fix for `risks`, and the `shrink-0` on the bullet dot. I
could not push to #4911 directly as it is on another contributor's fork.

@alxhrzg, if you would rather finish #4911 yourself, I am happy to close
this and hand over the tests and the two risk-row fixes for you to take
across.

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-12 16:08:22 -07: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.