Public: anyone can read this; the crew can change it.

The thread's comments are rows, not boxes #9701

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/9667-the-thread-s-comments-are-rows-not-boxes → main 5 commits fork 3 threads · 3 unresolved

  • Merged. scout/9667-the-thread-s-comments-are-rows-not-boxes was merged into main as 73fd254.

Review

Sign in to review this change.
4 files changed +35 −6
docs/design/areas/comments.md +4 −2
@@ -2,7 +2,9 @@
22
33 A comment on an issue or pull, and the form that writes one. Both are a `card()`; these classes give it its comment look.
44
5 - `.comment` — one comment as a card: its `.card-head` (byline and time) on the muted ground, the body under it. Written by `commentCard()` in `src/web/comments.ts` and by `reviewRows()` in `src/web/diff-view.ts` (an inline review comment); styled in `src/web/style/components.ts`, and spaced inside a review thread by `.dc-review .comment` in `src/web/style/content.ts`.
6 - `.comment-body` — a comment's rendered markdown, always beside `.prose`, padded inside its card. Written by `commentCard()` in `src/web/comments.ts`, `reviewRows()` in `src/web/diff-view.ts` and `reviewEntry()` in `src/web/studio/change.ts`; styled in `src/web/style/components.ts` (tighter in a timeline event's card).
5 - `.comment-row` — a thread comment as a row of the timeline, no box: `.comment-head` (byline and time) then the body as `.prose`, spaced by padding under its `.tl-item`, no hairline of its own (the timeline line is the only rule). Written by `commentCard()` in `src/web/comments.ts` (issue page, pull page, browse); styled in `src/web/style/components.ts`.
6 - `.comment-head` — the one line of byline, verb and time at the top of a `.comment-row`.
7 - `.comment` — an inline review comment as a card: its `.card-head` (byline and time) on the muted ground, the body under it. Written only by `reviewRows()` in `src/web/diff-view.ts`; styled in `src/web/style/components.ts`, and spaced inside a review thread by `.dc-review .comment` in `src/web/style/content.ts`.
outdated

Stale: .comment is no longer written by commentCard(); it is now only written by reviewRows(). Drop it from this line. The .comment-body entry below also says commentCard() writes it padded inside its card; say it is the row's body too (unpadded) and fix that.

8 - `.comment-body` — a comment's rendered markdown, always beside `.prose`: padded inside a `.comment` card, unpadded in a `.comment-row` (written by `commentCard()` in `src/web/comments.ts`); `reviewRows()` in `src/web/diff-view.ts` and `reviewEntry()` in `src/web/studio/change.ts`; styled in `src/web/style/components.ts` (tighter in a timeline event's card).
79 - `.comment-form` — the card that writes a comment: a textarea and its `.form-actions`. Written by `composer()` and `editForm()` in `src/web/pages/thread.ts`, `reviewRows()` in `src/web/diff-view.ts` (a reply in a review thread) and `studioApp()` in `src/web/studio/app.ts` (the "Tell the crew" box); styled in `src/web/style/components.ts`, with its narrow-screen action line and its review-thread margin in `src/web/style/content.ts`.
810 - `.tl-earlier` — the line above a long thread's newest items, `Show N earlier items`, a plain link to `?all=1`. Written by `conversation()` in `src/web/pages/thread.ts`; styled in `src/web/style/components.ts` beside `.tl-item`, indented to the timeline's item column.
src/web/comments.ts +3 −3
@@ -1,5 +1,5 @@
11 /**
2 * One comment card, shared by the full page render and the live-append
2 * One comment row (a row of the timeline, no box), shared by the full page render and the live-append
33 * fragment so a comment that arrives over SSE looks identical to one that was
44 * on the page at load. The `data-comment-id` is what the client tracks to know
55 * what it has already shown — it is load-bearing, not decoration.
@@ -38,8 +38,8 @@ export function commentCard(card: CommentCard): Html {
3838 card.originAuthor != null && card.originAuthor !== ""
3939 ? originTag(card.originAuthor, card.originKind ?? "globe")
4040 : userLink(card.author, { mark: card.mark });
41 return html`<div ${idAttr}class="card comment">
42 <div class="card-head">
41 return html`<div ${idAttr}class="comment-row">
42 <div class="comment-head">
4343 ${byline}
4444 <span class="muted">${card.verb} ${time(card.at)}</span>
4545 ${when(
src/web/style/components.ts +4 −1
@@ -877,13 +877,16 @@ details[open] > summary.row > .row-lead { transform: rotate(90deg); }
877877 min-height: 32px;
878878 }
879879 .tl-earlier { margin: 0; padding-left: 36px; color: var(--muted-fg); font-size: 13px; }
880 .tl-item[data-kind="comment"] .card { margin: 0; }
880 .tl-item[data-kind="comment"] { padding-bottom: 16px; }
outdated

Clean: commentCard and threadComment no longer emit a .card inside a comment tl-item, so .tl-item[data-kind="comment"] .card { margin: 0 } styles nothing now. Remove it.

881881 .tl-item[data-kind="event"] > .card { flex-basis: 100%; margin: 4px 0 0; color: var(--fg); font-size: 14px; }
outdated

Principles 3 and 6: a hairline under every comment plus the timeline line is a lot of rules. Prefer rhythm (padding between rows) and keep the hairline only where the thread ends, or show in the pull a look at 1440/390 that justifies it.

882882 .tl-item[data-kind="event"] > .card .comment-body { padding: 8px 12px; }
883883
884884 /* ---- comment card ---- */
885885 .comment .card-head { background: var(--muted); font-size: 13px; }
886886 .comment .comment-body { padding: 2px 16px 8px; }
887 .comment-row { min-width: 0; }
888 .comment-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; font-size: 13px; min-height: 28px; }
889 .comment-row .comment-body { padding: 4px 0 0; }
887890 .comment-form { margin-top: 16px; padding: 12px; overflow: visible; }
888891 .comment-form textarea { margin-bottom: 10px; }
889892
test/comment-row.test.ts added +24
@@ -0,0 +1,24 @@
1 import { expect, test } from "bun:test";
2 import { commentCard } from "../src/web/comments.ts";
3 import { threadComment } from "../src/web/pages/thread.ts";
4
5 const card = {
6 id: "c1",
7 author: "ada",
8 at: "2026-01-01T00:00:00Z",
9 body: "Hello **there**",
10 verb: "commented",
11 };
12
13 test("a comment is a row, not a .card", () => {
14 for (const out of [
15 String(commentCard(card)),
16 String(threadComment("c1", "ada", card.at, card.body)),
17 ]) {
18 expect(out).not.toMatch(/class="[^"]*\bcard\b/);
19 expect(out).not.toContain("card-head");
20 expect(out).toContain('class="comment-row"');
21 expect(out).toContain('class="prose comment-body"');
22 expect(out).toContain("<strong>there</strong>");
23 }
24 });

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs