From d7bcf3912f319c78e4eecec7baff7c80e55c4908 Mon Sep 17 00:00:00 2001 From: scotttong Date: Thu, 29 Oct 2026 14:42:14 -0700 Subject: [PATCH] fix(ui): single underline on markdown document card labels (#18215) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work > - Comments and task bodies render markdown, and a saved plan or document shows as a compact document card > - The document card is a link inside the markdown body, so it gets the general markdown link underline > - The card label also has its own underline, so the label shows two lines > - This pull request removes the inherited underline from document cards only > - The benefit is that the card label shows one clean underline ## Linked Issues or Issue Description No public issue exists. This is a small visual bug. **What happened?** A document card (for example the "plan" card) in a comment shows a double underline under its label. **Expected behavior** The card label shows a single underline. **Steps to reproduce** 1. Open a task that has a plan document. 2. Look at a comment or body that links the plan as a document card. 3. See two underlines under the card label. **Paperclip version or commit** `master` at 649354d8c. **Deployment mode** Local development (`pnpm dev`). The bug is in shared UI CSS, so it affects all modes. Related PRs: I searched for "document card underline" and found no duplicate PRs. ## What Changed - `ui/src/index.css`: add `.paperclip-markdown a[data-sidebar-document-link] { text-decoration: none; }` so the card does not inherit the markdown link underline. The label span keeps its own underline. - `ui/src/components/MarkdownDocumentCardStyles.test.ts`: add a stylesheet regression test. It uses the same pattern as `MarkdownAccentStyles.test.ts`. ## Verification - `pnpm --filter @paperclipai/ui exec vitest run src/components/MarkdownDocumentCardStyles.test.ts src/components/MarkdownExternalLink.test.tsx` passes (11 tests). - `node scripts/check-token-gates.mjs` reports all gates clean. - Manual: open a comment with a document card. The label shows one underline. Other markdown links keep their underline. - Snapshot baselines are intentionally not updated. See the `doc/design/DECISION-SHEET.md` entry "Per-change snapshot verification demoted to dormant (Jul 13 2026)". ## Risks - Low risk. The selector targets only anchors with `data-sidebar-document-link` inside `.paperclip-markdown`. Other links do not change. ## Model Used - Anthropic Claude Opus 5.5 (`claude-opus-5-5`), used through Claude Code with tool use (shell, file edit). Standard context window. No extended thinking mode. ## 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 --- .../MarkdownDocumentCardStyles.test.ts | 24 +++++++++++++++++++ ui/src/index.css | 6 +++++ 2 files changed, 30 insertions(+) create mode 100644 ui/src/components/MarkdownDocumentCardStyles.test.ts diff --git a/ui/src/components/MarkdownDocumentCardStyles.test.ts b/ui/src/components/MarkdownDocumentCardStyles.test.ts new file mode 100644 index 00000000000..3fcfd752a62 --- /dev/null +++ b/ui/src/components/MarkdownDocumentCardStyles.test.ts @@ -0,0 +1,24 @@ +import { readFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; + +const stylesheet = readFileSync(fileURLToPath(new URL("../index.css", import.meta.url)), "utf8"); + +function cssBlock(selector: string): string { + const start = stylesheet.indexOf(`${selector} {`); + expect(start, `Missing CSS selector: ${selector}`).toBeGreaterThanOrEqual(0); + + const bodyStart = stylesheet.indexOf("{", start); + const bodyEnd = stylesheet.indexOf("\n}", bodyStart); + expect(bodyStart, `Missing CSS block start: ${selector}`).toBeGreaterThanOrEqual(0); + expect(bodyEnd, `Missing CSS block end: ${selector}`).toBeGreaterThan(bodyStart); + + return stylesheet.slice(bodyStart + 1, bodyEnd); +} + +describe("markdown document card styles", () => { + it("removes the inherited markdown link underline so the card label shows one underline", () => { + expect(cssBlock(".paperclip-markdown a")).toContain("text-decoration: underline"); + expect(cssBlock(".paperclip-markdown a[data-sidebar-document-link]")).toContain("text-decoration: none"); + }); +}); diff --git a/ui/src/index.css b/ui/src/index.css index 15eff463006..d9e0f9aa191 100644 --- a/ui/src/index.css +++ b/ui/src/index.css @@ -2549,6 +2549,12 @@ a.paperclip-mention-chip[data-mention-kind="agent"]::before { text-decoration: none; } +/* Document cards underline only their label span; the card itself must not + inherit the markdown link underline, or the label shows two lines. */ +.paperclip-markdown a[data-sidebar-document-link] { + text-decoration: none; +} + .paperclip-markdown a.paperclip-mention-chip[data-mention-kind="issue"] { border-color: color-mix(in oklab, var(--foreground) 14%, var(--border) 86%); background: color-mix(in oklab, var(--accent) 42%, transparent);