openclaw / openclaw/openclaw

[Feature]: Standardize transcript artifact surfaces, spacing, and metadata

Open
#124,366 1 comment 1 reaction 1 assignee View on GitHub

@vyctorbrzezowski is already working on this.

Since Aug 16, 2026.

clawsweeper:needs-product-decision clawsweeper:no-new-fix-pr enhancement impact:ux-friction issue-rating: 🌊 off-meta tidepool maintainer P3
Dominant language
TypeScript
Stars
390k
Forks
82k
PR merge metrics
PR metrics pending

Description

Summary

Give the chat transcript one artifact grammar — a defined set of surface tiers, one spacing rhythm, one corner language, and one metadata treatment — so that turn bubbles, tool cards, output blocks, attachments, and system rows read as one designed stream instead of five components that happen to be stacked.

Related: #124362, #124363, #124364, #124365 — these five are one Control UI consistency pass. They share tokens and owner boundaries, so they should land as a coordinated series rather than independently.

Problem to solve

The transcript is the product's primary surface, and it is the one place where every artifact type in the Control UI appears side by side. There is no shared "transcript artifact" primitive. Each renderer was styled against its own neighbors, in its own stylesheet, and the four stylesheets never agreed.

The renderers, and where their rules live:

Artifact Component Stylesheet
Turn group, avatar, footer ui/src/pages/chat/components/chat-message-group.ts ui/src/styles/chat/grouped.css
User / assistant bubble, tool shell ui/src/pages/chat/components/chat-message-bubble.ts grouped.css, tool-cards.css
Markdown body (fences, tables, quotes) ui/src/pages/chat/components/chat-message-markdown.ts ui/src/styles/chat/text.css
Tool cards: collapsed row, blocks, diff, terminal, preview ui/src/pages/chat/components/chat-tool-cards.ts ui/src/styles/chat/tool-cards.css
Images / attachments / media chat-message-images.ts, chat-message-attachments.ts, chat-message-media.ts ui/src/styles/chat/layout.css:976-1227
Divider and system notice rows ui/src/pages/chat/components/chat-divider.ts grouped.css:347-470
Timestamp / token-cost metadata ui/src/pages/chat/components/chat-message-timestamp.ts grouped.css:288-293, 862-889

Row dispatch is at ui/src/pages/chat/components/chat-transcript-projection.ts:440-470, and every row lands as a sibling in .chat-thread-inner (ui/src/pages/chat/components/chat-thread.ts:93, styled at ui/src/styles/chat/layout.css:364-372).

Four surface treatments among direct siblings

All four are children of the same flex column (ui/src/styles/chat/grouped.css:89-99):

  • User bubblegrouped.css:551-565: background: var(--card), border: 1px solid transparent, border-radius: var(--radius-md), padding: 16px.
  • Assistant bubblegrouped.css:614-619: padding: 4px 0; border: 0; background: transparent; box-shadow: none.
  • Tool shellgrouped.css:597-605: padding: 0; border: 0; background: transparent; its summary row uses padding: 4px 8px and border-radius: var(--radius-sm) (tool-cards.css:28-51).
  • Attachment cardlayout.css:1112-1120: padding: 10px 12px, border: 1px solid var(--border), border-radius: var(--radius-md), background: color-mix(in srgb, var(--card) 82%, var(--bg)).

Inside one assistant turn the reader gets three different answers to "is this a thing or is this text?" stacked vertically: a bare tool row, bare prose, then a bordered filled card. The horizontal padding steps 0 → 4px → 8px → 12px → 16px with no shared token, so the left text edges of the four artifacts do not line up.

Spacing set per artifact type, in three mechanisms and two unit systems
  • Turn group: padding-bottom: 26pxgrouped.css:14-16. Under hover: none / max-width: 768px it switches mechanism to padding-bottom: 0; margin-bottom: 14pxgrouped.css:700-703.
  • Divider row: margin: 18px 8pxgrouped.css:350.
  • Notice row: margin: 14px auto; padding: 0 16pxgrouped.css:444-445.
  • Markdown blocks: margin-top: 0.75emtext.css:99-103, a relative unit that scales with --chat-text-size while every neighbor is fixed px.
  • Tool rows: margin-top: 2pxtool-cards.css:10; gap: 2pxtool-cards.css:523-526 and grouped.css:93.

Because .chat-thread-inner is a plain block, the group's 26px is padding and never collapses, while divider and notice use margins that collapse with each other. Rendered gaps: group→group 26px, group→divider 44px, divider→notice 18px. Scrolling the transcript, checkpoint dividers get nearly double the air of the messages around them while two consecutive system rows almost touch. The horizontal insets disagree too — groups 4px left / 16px right, dividers 8px both sides, notices centered — so the three row types have three different left edges.

Five treatments for the same output block

Same conceptual artifact (a scrollable block of preformatted output), at comparable depth:

Surface Citation Border Radius Fill
Markdown code fence text.css:482-489 1px solid var(--border-strong) --radius-md (10px) var(--bg-muted)
Tool block content tool-cards.css:682-694 none --radius-sm (6px) color-mix(--secondary 82%, transparent)
Tool terminal block tool-cards.css:386-391 none --radius-sm color-mix(--secondary 82%, transparent)
Inline diff tool-cards.css:302-312 none --radius-sm color-mix(--secondary 55%, transparent)
Attachment preview text layout.css:1211-1227 1px solid color-mix(--border 82%, transparent) --radius-sm color-mix(--bg 78%, transparent)

Their sibling in the same tool card, the preview frame (tool-cards.css:731-738), goes a sixth way: 1px solid color-mix(--border 78%, transparent), --radius-md, color-mix(--secondary 78%, transparent). It and .chat-tool-card__block-content are both margin-top: 8px children of the same card (tool-cards.css:643-646, 726-729), yet one is a framed 10px panel and the other an unframed 6px tint. Expanding a tool call shows command output as a soft borderless tint, then a code fence in the reply directly below it as a bordered, more-rounded card — same content type, two visual weights, four fill formulas across five blocks.

Literal-versus-token drift in the same family: border-radius: 8px on the image hover buttons (layout.css:1047), which is neither --radius-sm (6px) nor --radius-md (10px); border-radius: 999px on the attachment download button (layout.css:1178) where the rest of chat uses var(--radius-full) (9999px, base.css:234); and stale fallbacks var(--radius-sm, 4px) (grouped.css:303, 879, 959, 1027) and var(--radius-md, 8px) (grouped.css:985) encoding 4px/8px against real token values of 6px/10px.

Metadata rendered five ways

Same job — label the artifact:

  • Sender name: 12px / 500 / var(--muted), sentence case, in a footer hidden at opacity: 0 until hover — grouped.css:228-236, footer :125-141.
  • Timestamp and token cost: 12pxgrouped.css:288-293, 862-870.
  • Tool name: var(--control-ui-text-sm) / 500 / var(--text), sentence case, always visible, at the top of its artifact — tool-cards.css:102-110; its argument preview drops to mono at calc(var(--control-ui-text-sm) - 1px):112-122.
  • Tool block and preview labels: 11px / 600 / letter-spacing: 0.04em / uppercasetool-cards.css:674-680, 755-765.
  • Divider label: 600 / letter-spacing: 0.05em / uppercasegrouped.css:370-378, while its own metric span resets to 500 / text-transform: none (:392-397), so one line mixes uppercase-tracked and sentence-case text.
  • Attachment title: 13px / var(--text)layout.css:1148-1154.

Four font sizes, two casings at two different tracking values, and two placements. The tool name is a persistent label at the top of its artifact while the message author and time are hover-only at the bottom of theirs. Scanning a mixed transcript, tool artifacts read louder than message artifacts even though the message is the primary content.

What is already consistent

Worth stating, because it bounds the work and the fix should not disturb it:

  • The --radius-md tier is coherent — bubble, avatar, image frame, message image, markdown inline image, external-image placeholder, attachment card, and tool preview frame all use it. The corner problem is sm-versus-md across surface tiers, not scattered arbitrary radii.
  • Secondary metadata color is uniformly var(--muted). Nobody invented a bespoke grey.
  • Timestamp placement is uniform across roles; user, assistant, and tool groups share one .chat-group-footer. The placement split is group-footer versus tool-row-label, not user-versus-assistant.
  • Mono type is always var(--mono) at 12px across fences, tool body, terminal, diff, and attachment preview. The output blocks differ in chrome, not in type.

Proposed solution

Name the surface tiers, then assign every artifact to one. The transcript needs a small closed set — roughly: bare (assistant prose, tool summary rows), tinted (output blocks), and framed (user bubble, attachment card, preview frame) — each with one fill, one border, one radius, and one padding, defined once. Radius follows the tier, not the component, which is what removes the sm/md coin-flip.

One spacing owner. .chat-thread-inner becomes a flex column with a gap, so row rhythm is set once at the container instead of by five per-type margins and paddings that collapse against each other unpredictably. Per-row margin/padding-bottom spacing rules are deleted rather than tuned, including the mobile mechanism swap at grouped.css:700-703. Horizontal insets become one value for all row types.

One metadata treatment. One size, one weight, one casing for artifact labels, with the uppercase-tracked idiom either adopted everywhere or dropped everywhere. Persistent-versus-hover-revealed becomes a deliberate rule (for example: identity is always visible, secondary numbers reveal on hover) applied to all artifact types rather than differing by which component was written first.

Retire the literals. The 8px, 999px, and stale var(--radius-sm, 4px) / var(--radius-md, 8px) fallbacks resolve to the real tokens.

Fix the blockquote and its RTL twin while in the file. text.css:513-520 is the only markdown block with a hardcoded rgba(255,255,255,0.02) fill and an asymmetric radius, and its RTL variant at :557-562 uses border-right: 3px solid var(--border) against the LTR 2px solid var(--border-strong) — a different width and a different token, so RTL readers get a heavier, lower-contrast quote bar.

Net effect on production lines should be neutral or negative: one tier definition replacing per-component surface rules, and one container gap replacing five spacing mechanisms.

Alternatives considered

  • Only fix the output blocks. The most visible single win, and it leaves the spacing rhythm and metadata split — the two things that make the transcript feel unsettled while scrolling — untouched.
  • Only fix spacing. Cheapest structurally, but the surface and metadata divergence is what makes artifacts look like they came from different products.
  • Push everything to the framed tier so every artifact is a card. Rejected: the transcript would become a wall of boxes, and the assistant's prose is the content, not a widget. The tiers exist for a reason; they just need to be declared.
  • Leave it and style new artifacts case by case. This is the current policy, and it is what produced six output-block recipes.

Impact

Affected: every Control UI user. The transcript is the default view and the surface operators spend the most time reading.

Severity: cosmetic individually, structural in aggregate. The product's main surface reads as assembled rather than designed, and the uneven rhythm makes long transcripts measurably harder to scan.

Frequency: always. Any session containing a tool call, an attachment, and a checkpoint divider shows all four divergences at once.

Consequence: the primary surface undersells the product. Secondarily, ongoing drag — a new artifact type today has four stylesheets, six output-block recipes, and no tier definition to copy, so it adds a seventh.

Evidence/examples

File references are against current main. Reproducible with no configuration: open any session containing a tool call with expandable output, an assistant reply with a code fence, an attachment, and a checkpoint divider. The four divergences are visible in one screenful.

Before/after screenshots in dark and light are expected as PR evidence via the mocked-Gateway Playwright lane (OPENCLAW_CAPTURE_UI_PROOF=1), covering a transcript containing a user turn, an assistant turn with prose and a code fence, an expanded tool call with terminal output and a diff, an attachment card, an image, and a divider plus a system notice — the full artifact set in one capture, since the defect is only visible in comparison.

Do you plan to open a PR for this?

Yes, I plan to implement this myself

Acceptance criteria

  • A named, closed set of transcript surface tiers, each defined once, with every artifact assigned to one.
  • Radius follows the tier; no artifact picks --radius-sm versus --radius-md independently, and the 8px / 999px literals and the stale var(--radius-*, N) fallbacks are gone.
  • The five output-block recipes converge on one, or on a stated two-tier rule with the exception named in the PR.
  • Row rhythm owned by the container; per-row spacing margins deleted, including the mobile mechanism swap, and horizontal insets equal across row types.
  • One metadata type treatment and one deliberate persistent-versus-hover rule across artifact types.
  • The blockquote fill uses tokens, and its LTR and RTL variants agree on width and color token.
  • The consistencies listed above (the --radius-md tier, var(--muted) metadata color, uniform timestamp placement, uniform mono type) are preserved, not disturbed.
  • Before/after screenshots in dark and light of the full artifact set.
  • Production line count net-neutral or net-negative.

Maintainer note: this issue is tracked by a maintainer and usually has work already in progress. Please do not start working on it without checking with the maintainer first, unless you are a maintainer yourself.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.