Skip to content

refactor(ui): dedupe chat styles and make the styles.css import order real#99900

Merged
steipete merged 5 commits into
mainfrom
claude/sleepy-dubinsky-005dcc
Jul 4, 2026
Merged

refactor(ui): dedupe chat styles and make the styles.css import order real#99900
steipete merged 5 commits into
mainfrom
claude/sleepy-dubinsky-005dcc

Conversation

@steipete

@steipete steipete commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

What Problem This Solves

Fixes #99899.

ui/src/styles/components.css began with @import "./chat.css", and postcss-import's duplicate-skipping made that first occurrence win: every ui/src/styles/chat/*.css file loaded before the components.css body, while the later chat.css import in ui/src/styles.css (position 6) was dropped. The visible import order in styles.css was a lie, and components.css silently won all same-specificity ties against the chat stylesheets.

Around 35 selector groups were defined in both files with diverging values (.chat-text markdown rules, .chat-header*, .chat-controls*, .chat-session, .chat-new-messages, .chat-compose*, .chat-bubble, .chat-tool-card*, .chat-reading-indicator__dots, light-mode variants). Which copy rendered depended on the hidden hoist, and several chat-side declarations were dead but would resurrect (change rendering) on any import-graph change.

Why This Change Was Made

  • Each duplicated group is reconciled into one canonical definition in the owning chat/*.css file, carrying exactly the values that win on main today (per-property cascade merge; components.css won ties).
  • Chat-file declarations that are dead under the real cascade and would resurrect after the order flip are deleted or reordered instead of silently changing rendering: nested-blockquote border colors, chat-text block-art pre background, mobile pre/tool-card paddings, .chat-session 140px, tool-shell chrome, copy/expand button background + 14px svg sizing, pulsing-border/reading-pulse keyframes with their dead animation references, and the light bubble skin now sits below the role variants to keep its winning tie order.
  • The @import "./chat.css" line is removed, so the styles.css order is finally real.
  • The dead .chat-compose* family is deleted (no markup emits those classes anywhere; the live composer is agent-chat__input / chat-composer-*), including its synthetic fixture entries in ui/src/ui/form-controls.browser.test.ts.

Note for #99763: that redesign previously relied on pulsing-border (grouped.css) for streaming bubbles; this PR deletes that keyframes block because its only reference is dead on main (components' chatStreamPulse wins). The redesign can reintroduce it together with its new streaming rule when it lands on top of this.

User Impact

None intended: rendering is byte-for-byte identical. All 14 before/after screenshots (light+dark x desktop+mobile x top/markdown/bottom/new-messages-pill, fixed clock, animations disabled, deviceScaleFactor 2) are pixel-identical between origin/main and this branch: 0 differing pixels out of 8,256,000 (desktop) / 1,316,640 (mobile) per shot. Maintainers gain a truthful import order and single canonical definitions; the CSS bundle shrinks by ~380 lines.

Evidence

  • Screenshot methodology: mock-gateway harness (ui/src/test-helpers/control-ui-e2e.ts) rendering a markdown-heavy conversation (headings, lists, task lists, links, inline code, hljs + plain + block-art fences, 3-level nested blockquotes, tables, hr), tool rows, header/controls, composer, reading states; Playwright page.clock fixed to a constant so timestamps are stable; pixel comparison via pngjs. Result: all 14 shots 0-diff vs origin/main.
  • node scripts/run-vitest.mjs ui/src/ui/chat - 26 files, 394 tests passed on this branch.
  • node scripts/run-vitest.mjs run ui/src/ui/form-controls.browser.test.ts ui/src/ui/markdown.test.ts - 2 files, 93 tests passed (browser test runs real Chromium and asserts the touch-size contract for the remaining live controls).
  • Selector audit script (parses both sides, exact + :where()-loosened matching): 0 duplicated chat groups remain; the only flags left are the intentional higher-specificity .md-preview-dialog__reader overrides.
  • Codex autoreview (gpt-5.5) was run three times; all three findings were premise errors about the pre-diff cascade and are rejected with file/line proof. Example: it claimed bare block-art pres computed background: var(--bg) on main via components.css:2224, but components.css's own .chat-text :where(pre) at line 4171 sets var(--secondary) later at equal specificity, so main renders var(--secondary) - exactly what the canonical rule now carries. The empirical 0-pixel diffs settle the remaining claims (e.g. main's copy/expand buttons demonstrably render 16px icons on --bg-elevated, proving components.css loaded last pre-diff).

@openclaw-barnacle openclaw-barnacle Bot added app: web-ui App: web-ui size: L maintainer Maintainer-authored PR labels Jul 4, 2026
@clawsweeper

clawsweeper Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs changes before merge. Reviewed July 4, 2026, 4:54 AM ET / 08:54 UTC.

Summary
The PR removes the hidden chat import from components.css, consolidates duplicated chat CSS selectors into ui/src/styles/chat/*, and deletes dead .chat-compose* fixture/style coverage.

PR surface: Source -406, Tests -2. Total -408 across 7 files.

Reproducibility: yes. The duplicate import and the table-spacing regression are source-reproducible by comparing current components.css cascade rules with the PR head chat/text.css rule; I did not run the pixel harness in this read-only review.

Review metrics: none identified.

Root-cause cluster
Relationship: fixed_by_candidate
Canonical: #99899
Summary: This PR is the candidate fix for the linked duplicate-chat-CSS issue; the tool-row redesign is adjacent because it also noticed the CSS ordering problem.

Members:

Proposal only: this assessment does not dispatch repair, suppress jobs, mutate sibling items, close, or merge anything.

Merge readiness
Overall: 🦐 gold shrimp
Proof: 🦞 diamond lobster
Patch quality: 🦐 gold shrimp
Result: needs maintainer review before merge.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • Restore current table spacing semantics in ui/src/styles/chat/text.css.
  • Rerun or extend the visual proof with a markdown message that starts with a table.

Mantis proof suggestion
A short browser visual proof for markdown table spacing would materially help verify the CSS cascade cleanup after the finding is fixed. A maintainer can ask Mantis to capture proof by posting this exact PR comment:

@openclaw-mantis visual task: verify Control UI chat markdown table spacing before and after this CSS dedupe, including a message that starts with a table.

Risk before merge

  • [P1] The reported pixel proof does not appear to cover a chat markdown message where a table is the first block or follows non-paragraph content, which is where the PR changes spacing.

Maintainer options:

  1. Decide the mitigation before merge
    Keep the CSS ownership cleanup, but preserve current chat markdown table spacing semantics before merge and rerun the visual/table coverage that supports the no-rendering-change claim.
  2. Pause or close
    Do not merge this PR until maintainers decide whether the risk is worth taking.

Next step before merge

  • There is one narrow mechanical CSS blocker: preserve current chat table spacing while keeping the dedupe/refactor intact.

Security
Cleared: The diff is limited to UI CSS and one browser test fixture, with no dependency, workflow, credential, build, or package execution changes.

Review findings

  • [P2] Preserve the existing leading-table spacing — ui/src/styles/chat/text.css:35-36
Review details

Best possible solution:

Keep the CSS ownership cleanup, but preserve current chat markdown table spacing semantics before merge and rerun the visual/table coverage that supports the no-rendering-change claim.

Do we have a high-confidence way to reproduce the issue?

Yes. The duplicate import and the table-spacing regression are source-reproducible by comparing current components.css cascade rules with the PR head chat/text.css rule; I did not run the pixel harness in this read-only review.

Is this the best way to solve the issue?

No. The ownership move is the right shape, but the implementation should keep table margins at the current computed behavior instead of making every chat table start with top margin.

Full review comments:

  • [P2] Preserve the existing leading-table spacing — ui/src/styles/chat/text.css:35-36
    Current main only gives tables top spacing for the p + table case; a table that starts a chat message, or follows a list/blockquote, has zero top margin after the shared reset. This PR changes the canonical rule to give every chat table margin-top: 0.75em, so those messages shift vertically even though the PR promises byte-for-byte identical rendering. Please keep the current sibling-specific spacing when moving the rule into chat/text.css.
    Confidence: 0.9

Overall correctness: patch is incorrect
Overall confidence: 0.88

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 3d404478b802.

Label changes

Label changes:

  • add P3: This is a Control UI CSS cleanup/refactor with limited blast radius and no intended feature or runtime behavior change.
  • add proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-change UI proof via reported before/after pixel comparisons across light/dark and desktop/mobile plus targeted browser/Vitest runs, though the table-first case still needs correction.
  • add rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (live_output): The PR body includes after-change UI proof via reported before/after pixel comparisons across light/dark and desktop/mobile plus targeted browser/Vitest runs, though the table-first case still needs correction.

Label justifications:

  • P3: This is a Control UI CSS cleanup/refactor with limited blast radius and no intended feature or runtime behavior change.
  • rating: 🦐 gold shrimp: Overall readiness is 🦐 gold shrimp; proof is 🦞 diamond lobster and patch quality is 🦐 gold shrimp.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action. Sufficient (live_output): The PR body includes after-change UI proof via reported before/after pixel comparisons across light/dark and desktop/mobile plus targeted browser/Vitest runs, though the table-first case still needs correction.
  • proof: sufficient: Contributor real behavior proof is sufficient. The PR body includes after-change UI proof via reported before/after pixel comparisons across light/dark and desktop/mobile plus targeted browser/Vitest runs, though the table-first case still needs correction.
Evidence reviewed

PR surface:

Source -406, Tests -2. Total -408 across 7 files.

View PR surface stats
Area Files Added Removed Net
Source 6 137 543 -406
Tests 1 0 2 -2
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 0 0 0 0
Total 7 137 545 -408

Acceptance criteria:

  • [P1] node scripts/run-vitest.mjs run ui/src/ui/markdown.test.ts ui/src/ui/form-controls.browser.test.ts.
  • [P1] node scripts/run-vitest.mjs ui/src/ui/chat.

What I checked:

  • Protected label: Live GitHub metadata shows this PR is open and labeled maintainer, so it is not eligible for conservative auto-close cleanup.
  • Current import graph: Current main imports components.css before chat.css, while components.css also imports ./chat.css at its own top level. (ui/src/styles.css:1, 3d404478b802)
  • Hidden nested import: Current main has @import "./chat.css" as line 1 of components.css, matching the PR's central cleanup target. (ui/src/styles/components.css:1, 3d404478b802)
  • Dependency import contract: Vite 8.0.16 runs postcss-import when CSS contains @import, and postcss-import defaults skipDuplicates to true and skips already imported files in the same scope. (packages/vite/src/node/plugins/[email protected]:1547)
  • Duplicate-skip behavior: postcss-import 16.1.1 tracks imported files by filename/scope and returns early when the same file has already been imported. ([email protected]/lib/parse-styles.js:156)
  • Dead compose selector check: chat-compose* appears only in CSS and the synthetic form-controls browser fixture; current live composer markup uses agent-chat__input / agent-chat__composer-combobox. (ui/src/ui/views/chat.ts:2722, 3d404478b802)

Likely related people:

  • steipete: Recent merged Control UI chat/style commits touched ui/src/styles/chat/layout.css, ui/src/styles/chat/tool-cards.css, and the current PR's cleanup area. (role: recent area contributor; confidence: high; commits: bb724436150e, 2b30951b8090; files: ui/src/styles/chat/layout.css, ui/src/styles/chat/tool-cards.css, ui/src/styles/components.css)
  • BunsDev: Path history shows repeated Control UI/chat styling and UI architecture work, including dashboard/chat infrastructure, a11y styling, text sizing, and tool-card polish. (role: feature-history contributor; confidence: medium; commits: 0849cac106d1, 52370c59980b, df72ca1ecec7; files: ui/src/styles/components.css, ui/src/styles/chat/grouped.css, ui/src/styles/chat/tool-cards.css)
  • macdao: The mobile chat layout commit explicitly mentions reverting chat.css import order and moving chat layout styles between components.css and chat/layout.css. (role: historical import/layout contributor; confidence: medium; commits: 334c4be73e1b; files: ui/src/styles/components.css, ui/src/styles/chat/layout.css, ui/src/styles/chat/grouped.css)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d9f97ea39a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@@ -1,5 +1,3 @@
@import "./chat.css";

/* ===========================================

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep streaming pulse after the import reorder

With this import gone, components.css now loads before chat/grouped.css; however components.css:3979 still owns .chat-bubble.streaming while chat/grouped.css:412 later sets .chat-bubble.fade-in. Streaming bubbles include both classes in ui/src/ui/chat/grouped-render.ts:1877, so the later animation: fade-in ... shorthand overrides chatStreamPulse (and the reduced-motion animation: none) whenever a response is streaming. Move or reconcile the streaming rule after the fade-in rule so streaming messages keep the old pulse behavior.

Useful? React with 👍 / 👎.

@clawsweeper clawsweeper Bot added proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. labels Jul 4, 2026
@steipete
steipete requested a review from a team as a code owner July 4, 2026 10:41
@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation channel: imessage Channel integration: imessage channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web gateway Gateway runtime extensions: memory-core Extension: memory-core security Security documentation scripts Repository scripts docker Docker and sandbox tooling labels Jul 4, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a6132b9dc5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +134 to +136
const storePath = runtime.agent.session.resolveStorePath(undefined, {
agentId: context.agentId,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve the active session store for Codex thread bindings

When a user configures session.store, this still resolves the default store because undefined is passed here and getSessionEntry above also reads without a store path. The new fork/archive binding operations then read or write the .codex sidecar next to the default transcript, while the Codex app-server run path reads bindings from the actual active sessionFile, so custom-store sessions either fail to attach the fork or attach it somewhere the next turn will not see.

Useful? React with 👍 / 👎.

Comment on lines +290 to +294
const response = await request(
pluginConfig,
CODEX_CONTROL_METHODS.forkThread,
{ threadId, threadSource: "user" },
requestOptions(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow the fork action through the sandbox guard

In sandboxed OpenClaw sessions, this request is rejected before it reaches Codex because extensions/codex/src/app-server/sandbox-guard.ts still classifies thread/fork as blocked-native-bypass (only name/archive/unarchive were allowed in the new test). Since codex_threads exposes fork as the safe way to continue a native thread, owner users with sandboxing enabled will see the advertised action fail every time unless the guard is updated or the tool hides/rejects the action earlier.

Useful? React with 👍 / 👎.

@steipete
steipete merged commit 33022da into main Jul 4, 2026
98 checks passed
@steipete
steipete deleted the claude/sleepy-dubinsky-005dcc branch July 4, 2026 11:03
@steipete

steipete commented Jul 4, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request Jul 5, 2026
… real (openclaw#99900)

* refactor(ui): dedupe chat styles and make styles.css import order real

* refactor(ui): dedupe chat tool-row styles into chat files

* refactor(ui): keep light bubble skin after role variants

* refactor(ui): keep light bubble skin after role variants

* refactor(ui): keep light bubble skin after role variants
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: web-ui App: web-ui channel: imessage Channel integration: imessage channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web docker Docker and sandbox tooling docs Improvements or additions to documentation extensions: acpx extensions: codex extensions: device-pair extensions: memory-core Extension: memory-core extensions: memory-wiki extensions: phone-control gateway Gateway runtime maintainer Maintainer-authored PR P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. plugin: migrate-claude plugin: migrate-hermes proof: sufficient ClawSweeper judged the real behavior proof convincing. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. scripts Repository scripts security Security documentation size: XL status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Control UI: duplicate @import makes components.css silently win over chat/*.css; ~35 selector groups defined twice

1 participant