fix(ui): align runner commentary with chat replies (#14716)

## Thinking Path

> - Paperclip helps people manage AI agents and their work.
> - Agent chat shows run commentary and the agent's reply in one thread.
> - The runner commentary text starts 4px left of the following reply.
> - The runner activity group omits the gutter that the reply bubble
uses.
> - This pull request gives runner commentary the same token-based
gutter.
> - Both text blocks now share one left edge.

## Linked Issues or Issue Description

**What happened?**

In agent chat, the first commentary line of a completed run starts about
4px left of the following agent reply. This is visible in the Codie
conversation.

**Expected behavior**

Runner commentary and ordinary agent reply text should have the same
left edge.

**Steps to reproduce**

1. Open an agent chat with a completed run that has commentary and an
agent reply.
2. Compare the first commentary line with the following reply at the
left edge.
3. Inspect the wrappers. Before this change,
`task-chat-phase-interstitial` has zero left padding and
`task-chat-agent-bubble` has 4px.

## What Changed

- Add the existing `px-1` spacing token to runner commentary.
- Add a regression test that compares the runner commentary gutter with
the ordinary agent reply gutter.

## Verification

- `pnpm check:token-gates`
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat/TaskChatRunnerActivityGroup.test.tsx
src/components/task-chat/TaskChatTurn.test.tsx
src/components/task-chat/TaskChatBubble.test.tsx`
- `pnpm --filter @paperclipai/ui typecheck`
- `pnpm --filter @paperclipai/ui build`
- A full repository typecheck and build passed earlier on this branch.
All latest-commit CI gates passed, including the server shard after a
transient preview readiness failure was rerun.
- Browser render: runner commentary and reply paragraphs both start at
x=32px after the change.

## Risks

Low risk. This changes only the horizontal padding of runner commentary.
Long commentary lines may wrap 8px sooner. The full local test suite was
stopped after the code changed during its run; the focused UI tests and
latest-commit CI suite passed.

> I checked `ROADMAP.md`. This bug fix does not duplicate planned core
work.

## Model Used

OpenAI GPT-6 in Codex. The runtime did not expose a more specific model
ID or context window size. The agent used reasoning, shell tools, and
read-only browser inspection to diagnose the layout and verify the
change.

## 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
- [x] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

---------

Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip authored and GitHub committed 2026-09-30 09:48:41 -05:00
1 parent d30b03bd8c
commit 44736c9c7c
2 files changed
+25 -1

No files matched your search

@@ -6,6 +6,7 @@ import { TaskChatThreadView } from "./TaskChatThreadView";
import { ThemeProvider } from "@/context/ThemeContext";
import { MemoryRouter } from "@/lib/router";
import { TaskChatRunnerActivityGroup } from "./TaskChatRunnerActivityGroup";
import { TaskChatBubble } from "./TaskChatBubble";
import { TaskChatExpansionState } from "./expansion-state";
import type {
TaskChatActivityPhaseItem,
@@ -73,6 +74,29 @@ describe("TaskChatRunnerActivityGroup", () => {
const viewport = () =>
container.querySelector('[data-testid="task-chat-activity-viewport"]')!;
it("aligns runner commentary with the following agent reply", () => {
act(() => root.render(
<MemoryRouter>
<ThemeProvider>
<TaskChatRunnerActivityGroup item={{
id: "commentary:phase",
kind: "activity_phase",
interstitial: { id: "commentary", kind: "message", author: "agent", text: "First line" },
items: [],
active: false,
summary: "",
}} />
<TaskChatBubble item={{ id: "reply", kind: "message", author: "agent", text: "Later reply" }} />
</ThemeProvider>
</MemoryRouter>,
));
const commentary = container.querySelector('[data-testid="task-chat-phase-interstitial"]');
const reply = container.querySelector('[data-testid="task-chat-agent-bubble"]');
expect(commentary?.classList.contains("px-1")).toBe(true);
expect(reply?.classList.contains("px-1")).toBe(true);
});
it("rolls to each new item once while status and token updates keep the current row mounted", () => {
render([tool("one")]);
const first = viewport().querySelector('[data-activity-row="one"]');
@@ -333,7 +333,7 @@ export function TaskChatRunnerActivityGroup({
>
{item.interstitial ? (
<div
className="min-w-0 text-sm text-foreground/90"
className="min-w-0 px-1 text-sm text-foreground/90"
data-testid="task-chat-phase-interstitial"
>
<MarkdownBody softBreaks linkIssueReferences>