Skip to content

desktop: One Dark Pro workspace messages have low contrast #50986

Open

Description

@anabelle2001

Description

Summary

With One Dark Pro, user messages are illegible (1.2 to 1 contrast ratio) when in a "workspace session". "local sessions", in contrast, are highly legible (14.1 to 1 contrast ratio). Screenshots below show the difference.

Expected behavior

User messages remain legible in both session types with this theme.

Actual behavior

One Dark Pro dark Local Workspace
CSS text token --v2-blue-300 --v2-text-text-contrast
Sampled text #d5ebff #a8afbc
CSS background token --v2-blue-1200 --v2-background-bg-accent
Sampled background #021c31 #3eacff
Text contrast 14.1:1 1.2:1

Plugins

opencode-mystatus

OpenCode version

2.0.6 and 2.0.15

Steps to reproduce

  1. In the desktop app, choose One Dark Pro and use dark mode.
  2. Open a session in one of the project's workspace directories, distinct from its main worktree, and view a user message.
  3. Compare the message with one in a session in the project's main worktree.

Screenshot and/or share link

Local session Workspace session
Local session, readable dark-blue user message Workspace session, pale text on bright-blue user message

Operating System

Windows 11 (10.0.26200.0)

Terminal

OpenCode desktop app

Activity

  1. argszero commented on Sep 24, 2026

    @argszero
    Contributor

    I would like to work on this.

    I confirmed the pairing on the current v2 head using the token values from the report: #a8afbc on #3eacff is 1.11:1, against 14.14:1 for the local-session pair. There is a ceiling here that rules out the obvious fix, so it is worth stating first: #3eacff has a relative luminance of 0.3775, which makes white on it only 2.46:1 - no light foreground can reach the 4.5:1 threshold. The paired foreground has to go dark (black reaches 8.55:1; clearing 4.5:1 needs a luminance of at most 0.045), or the surface token has to change.

    Why I read this as a defect rather than theme taste: the icon half of the same semantic role is already contrast-picked. packages/ui/src/theme/v2/foreground.ts:57 derives v2-icon-icon-contrast through pickGrey(primitives, bgContrast, 7, 100), so it lands on a grey that clears 7:1 against the surface it is used on. The text half is a static ref("v2-grey-100") in packages/ui/src/theme/v2/mapping.ts (:48 light, :116 dark) and is never checked against any surface, and neither token is checked against background-bg-accent, which is the surface the workspace message bubble pairs it with. One Dark Pro defines only a palette, so it inherits a bright blue-600 (palette.primary #61afef through resolve.ts:112) and a grey ramp whose lightest step is mid-tone (palette.ink #abb2bf through :111), which is what produces the sampling in the report.

    Plan: if text-text-contrast has to stay tied to the contrast surface (it is what bg-contrast pairs with), then by AGENTS.md this is the case where the theme does not expose a token for the required semantic role - the accent-surface foreground role has to be added across the schema, defaults, resolution and types, and used in the workspace bubble. Otherwise derive the contrast text with the same contrast-aware helper the icon tokens use, with the accent surface included in the set it is checked against. Either way the change lands in packages/ui/src/theme/v2, with a case added to theme/v2/resolve.test.ts so every built-in theme has to clear the threshold on each paired surface, in light and dark. The pre-work on my side is pinning the component that renders that bubble so the token change and its call site land together.

  2. argszero commented on Sep 24, 2026

    @argszero
    Contributor

    Fix-site update after reading the v2 sources, so the scope is unambiguous when someone picks this up.

    The pairing is one CSS rule. packages/session-ui/src/components/message-part.css:1472-1477 is the workspace-session bubble:

    :root body [data-workspace-session] [data-component="user-message"] [data-slot="user-message-text"] {
      --user-message-mention-prefix-color: var(--v2-blue-300);
      background: var(--v2-background-bg-accent);
      color: var(--v2-text-text-contrast);
    }
    

    The drift is visible right below it. The local-session rules at :1479-1497 are per-color-scheme and pass comfortably - light --v2-blue-100 on --v2-blue-700 is 7.03:1, dark --v2-blue-1200 on --v2-blue-300 is 11.53:1. The workspace rule is the only one of the three that is not scheme-branched and the only one that pairs a fixed semantic foreground with a theme-derived surface.

    Why the default theme escapes and One Dark Pro does not. oc-2 overrides v2-blue-600 explicitly, so bg-accent stays #3b5cf6 in both schemes and white on it is 5.19:1. One Dark Pro ships only a palette, so bg-accent is derived from palette.primary (resolve.ts:112) and its dark value lands at #3eacff, where - as noted before - even pure white only reaches 2.46:1. Any custom theme with a light primary has the same problem.

    There is an existing e2e regression that pins the current pairing, so it has to move with the fix: packages/app/e2e/regression/workspace-accent.spec.ts:78-87 asserts exactly these two tokens for the accent scenarios and runs them under both light and dark.

    Two shapes, and I would rather a maintainer pick:

    1. Derive it, the way icons already do. The icon half of this role is contrast-picked against the surface it sits on (theme/v2/foreground.ts:57, pickGrey(primitives, bgContrast, 7, 100)); the text half is a static ref("v2-grey-100") in theme/v2/mapping.ts (:48 light, :116 dark). Following AGENTS.md, the accent-surface foreground is a semantic role the theme does not currently expose, so this means adding it across schema, defaults, resolution and types, resolving it contrast-aware against bg-accent, and using it here - plus verifying the built-in light and dark defaults and the custom-theme fallback, since it changes the public token surface.
    2. Reuse the precedent in the same file: give the workspace bubble the same per-scheme treatment its two neighbours already have, keeping the accent background that distinguishes a workspace session and choosing the dark-scheme foreground so it clears the threshold on a bright accent.

    (1) fixes every theme; (2) is a much smaller diff but leaves the next bright-accent theme to hit the same wall. I am happy to do either - say which and I will open the PR.

  3. argszero commented on Sep 25, 2026

    @argszero
    Contributor

    Fix-site correction after re-reading the current head, so the pointer is exact.

    My earlier comment pointed at the generic user-message block (:1423-1428, background: var(--v2-background-bg-layer-01)). That is the fallback rule. The rule that actually produces the reported pairing is :1472-1477:

    :root body [data-workspace-session] [data-component="user-message"] [data-slot="user-message-text"] {
      --user-message-mention-prefix-color: var(--v2-blue-300);
    
      background: var(--v2-background-bg-accent);
      color: var(--v2-text-text-contrast);
    }
    

    The fix shape is already in this file, twenty lines below. The local-session halves (:1479-1487 light, :1489-1497 dark) each pair a chosen background ramp with a chosen text ramp (blue-100 on blue-700, blue-1200 on blue-300). That is why local sessions read fine and workspace sessions do not. The workspace block is the one place that pairs a surface token with the generic contrast token without checking that the two work together. It also hardcodes --user-message-mention-prefix-color: var(--v2-blue-300) inside the same block, and that sits on the derived accent surface too.

    Numbers, re-verified against the current head: 1.11:1 for the workspace pair, against 14.14:1 for the local pair. Ceiling: white on that accent is 2.46:1 and the surface luminance is 0.3775, so reaching 4.5:1 requires a foreground with luminance at most 0.045. Any fix that lightens the text cannot pass; the fix must go darker or change the surface.

    Two shapes, both small:

    1. Give the workspace-session block its own explicit pair, mirroring the local-session halves right below it. Local to one file, and it follows the pattern already established in this file.
    2. Keep text-text-contrast for the generic case and derive an accent-surface foreground with the contrast-aware helper the icon tokens already use (theme/v2/foreground.ts:57). More correct, but it changes the public token surface, so per AGENTS.md it also requires re-verifying the light and dark defaults and the custom-theme fallback.

    I can open the PR against either shape, whichever you prefer. The regression anchor for both is the pairing pinned by packages/app/e2e/regression/workspace-accent.spec.ts (assertions at :71-79).

  4. argszero commented on Sep 26, 2026

    @argszero
    Contributor

    Releasing my claim here so my in-flight work stays bounded — I am picking up a smaller target on the default branch instead. Nothing is withdrawn as analysis: the measured numbers and the corrected fix site in my earlier comments still stand (packages/session-ui/src/components/message-part.css:1472-1477, with the :1479-1497 local-session halves as the precedent in the same file). Anyone is welcome to take it, and I am happy to hand over the details.

  5. opencodejr commented on Oct 28, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report, including the token names, sampled colours and contrast ratios. That makes this much easier to look into.

    I couldn't reproduce it here: this sandbox can't run the desktop app on Windows, so a maintainer will need to check it in the desktop app.

    From reading the code, your numbers look consistent with how themes are built. The accent background (--v2-background-bg-accent) comes from the theme's primary colour. For One Dark Pro dark that gives a light blue, and the contrast text (--v2-text-text-contrast) is a light grey, so the two clash. I haven't confirmed this in the app. As a possible workaround until this is looked at, a theme with a darker primary colour might avoid the low contrast in workspace sessions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions