From 57030dc13c095b23194063bf99a16a4ffcf6e460 Mon Sep 17 00:00:00 2001 From: divyanshu-iitian <23f2001496@ds.study.iitm.ac.in> Date: Thu, 23 Jul 2026 16:48:56 +0530 Subject: [PATCH 1/3] fix(web): support opacity modifiers for theme colors --- packages/web/tailwind.config.ts | 149 ++++++++++++++-------------- packages/web/tailwindConfig.test.ts | 37 +++++++ 2 files changed, 113 insertions(+), 73 deletions(-) create mode 100644 packages/web/tailwindConfig.test.ts diff --git a/packages/web/tailwind.config.ts b/packages/web/tailwind.config.ts index 1bd0cf32c..77036eb7d 100644 --- a/packages/web/tailwind.config.ts +++ b/packages/web/tailwind.config.ts @@ -1,5 +1,8 @@ import type { Config } from "tailwindcss" +const withOpacity = (variable: `--${string}`) => + `color-mix(in srgb, var(${variable}) calc( * 100%), transparent)` + const config = { darkMode: ["class"], content: [ @@ -19,103 +22,103 @@ const config = { }, extend: { colors: { - border: 'var(--border)', - input: 'var(--input)', - ring: 'var(--ring)', - background: 'var(--background)', - backgroundSecondary: 'var(--background-secondary)', - foreground: 'var(--foreground)', + border: withOpacity('--border'), + input: withOpacity('--input'), + ring: withOpacity('--ring'), + background: withOpacity('--background'), + backgroundSecondary: withOpacity('--background-secondary'), + foreground: withOpacity('--foreground'), primary: { - DEFAULT: 'var(--primary)', - foreground: 'var(--primary-foreground)' + DEFAULT: withOpacity('--primary'), + foreground: withOpacity('--primary-foreground') }, secondary: { - DEFAULT: 'var(--secondary)', - foreground: 'var(--secondary-foreground)' + DEFAULT: withOpacity('--secondary'), + foreground: withOpacity('--secondary-foreground') }, destructive: { - DEFAULT: 'var(--destructive)', - foreground: 'var(--destructive-foreground)' + DEFAULT: withOpacity('--destructive'), + foreground: withOpacity('--destructive-foreground') }, muted: { - DEFAULT: 'var(--muted)', - foreground: 'var(--muted-foreground)', - accent: 'var(--muted-accent)' + DEFAULT: withOpacity('--muted'), + foreground: withOpacity('--muted-foreground'), + accent: withOpacity('--muted-accent') }, accent: { - DEFAULT: 'var(--accent)', - foreground: 'var(--accent-foreground)' + DEFAULT: withOpacity('--accent'), + foreground: withOpacity('--accent-foreground') }, popover: { - DEFAULT: 'var(--popover)', - foreground: 'var(--popover-foreground)' + DEFAULT: withOpacity('--popover'), + foreground: withOpacity('--popover-foreground') }, card: { - DEFAULT: 'var(--card)', - foreground: 'var(--card-foreground)' + DEFAULT: withOpacity('--card'), + foreground: withOpacity('--card-foreground') }, - highlight: 'var(--highlight)', - link: 'var(--link)', - shell: 'var(--shell)', + highlight: withOpacity('--highlight'), + link: withOpacity('--link'), + shell: withOpacity('--shell'), sidebar: { - DEFAULT: 'var(--sidebar-background)', - foreground: 'var(--sidebar-foreground)', - primary: 'var(--sidebar-primary)', - 'primary-foreground': 'var(--sidebar-primary-foreground)', - accent: 'var(--sidebar-accent)', - 'accent-foreground': 'var(--sidebar-accent-foreground)', - border: 'var(--sidebar-border)', - ring: 'var(--sidebar-ring)' + DEFAULT: withOpacity('--sidebar-background'), + foreground: withOpacity('--sidebar-foreground'), + primary: withOpacity('--sidebar-primary'), + 'primary-foreground': withOpacity('--sidebar-primary-foreground'), + accent: withOpacity('--sidebar-accent'), + 'accent-foreground': withOpacity('--sidebar-accent-foreground'), + border: withOpacity('--sidebar-border'), + ring: withOpacity('--sidebar-ring') }, - warning: 'var(--warning)', - error: 'var(--error)', + warning: withOpacity('--warning'), + error: withOpacity('--error'), editor: { - background: 'var(--editor-background)', - foreground: 'var(--editor-foreground)', - caret: 'var(--editor-caret)', - selection: 'var(--editor-selection)', - selectionMatch: 'var(--editor-selection-match)', - gutterBackground: 'var(--editor-gutter-background)', - gutterForeground: 'var(--editor-gutter-foreground)', + background: withOpacity('--editor-background'), + foreground: withOpacity('--editor-foreground'), + caret: withOpacity('--editor-caret'), + selection: withOpacity('--editor-selection'), + selectionMatch: withOpacity('--editor-selection-match'), + gutterBackground: withOpacity('--editor-gutter-background'), + gutterForeground: withOpacity('--editor-gutter-foreground'), gutterBorder: 'var(--editor-gutter-border)', - gutterActiveForeground: 'var(--editor-gutter-active-foreground)', - lineHighlight: 'var(--editor-line-highlight)', + gutterActiveForeground: withOpacity('--editor-gutter-active-foreground'), + lineHighlight: withOpacity('--editor-line-highlight'), tag: { - keyword: 'var(--editor-tag-keyword)', - name: 'var(--editor-tag-name)', - function: 'var(--editor-tag-function)', - label: 'var(--editor-tag-label)', - constant: 'var(--editor-tag-constant)', - definition: 'var(--editor-tag-definition)', - brace: 'var(--editor-tag-brace)', - type: 'var(--editor-tag-type)', - operator: 'var(--editor-tag-operator)', - tag: 'var(--editor-tag-tag)', - 'bracket-square': 'var(--editor-tag-bracket-square)', - 'bracket-angle': 'var(--editor-tag-bracket-angle)', - attribute: 'var(--editor-tag-attribute)', - string: 'var(--editor-tag-string)', + keyword: withOpacity('--editor-tag-keyword'), + name: withOpacity('--editor-tag-name'), + function: withOpacity('--editor-tag-function'), + label: withOpacity('--editor-tag-label'), + constant: withOpacity('--editor-tag-constant'), + definition: withOpacity('--editor-tag-definition'), + brace: withOpacity('--editor-tag-brace'), + type: withOpacity('--editor-tag-type'), + operator: withOpacity('--editor-tag-operator'), + tag: withOpacity('--editor-tag-tag'), + 'bracket-square': withOpacity('--editor-tag-bracket-square'), + 'bracket-angle': withOpacity('--editor-tag-bracket-angle'), + attribute: withOpacity('--editor-tag-attribute'), + string: withOpacity('--editor-tag-string'), link: 'var(--editor-tag-link)', - meta: 'var(--editor-tag-meta)', - comment: 'var(--editor-tag-comment)', + meta: withOpacity('--editor-tag-meta'), + comment: withOpacity('--editor-tag-comment'), emphasis: 'var(--editor-tag-emphasis)', heading: 'var(--editor-tag-heading)', - atom: 'var(--editor-tag-atom)', - processing: 'var(--editor-tag-processing)', - separator: 'var(--editor-tag-separator)', - invalid: 'var(--editor-tag-invalid)', - quote: 'var(--editor-tag-quote)', - 'annotation-special': 'var(--editor-tag-annotation-special)', - number: 'var(--editor-tag-number)', - regexp: 'var(--editor-tag-regexp)', - 'variable-local': 'var(--editor-tag-variable-local)' + atom: withOpacity('--editor-tag-atom'), + processing: withOpacity('--editor-tag-processing'), + separator: withOpacity('--editor-tag-separator'), + invalid: withOpacity('--editor-tag-invalid'), + quote: withOpacity('--editor-tag-quote'), + 'annotation-special': withOpacity('--editor-tag-annotation-special'), + number: withOpacity('--editor-tag-number'), + regexp: withOpacity('--editor-tag-regexp'), + 'variable-local': withOpacity('--editor-tag-variable-local') } }, chat: { - reference: 'var(--chat-reference)', - 'reference-hover': 'var(--chat-reference-hover)', - 'reference-selected': 'var(--chat-reference-selected)', - 'reference-selected-border': 'var(--chat-reference-selected-border)' + reference: withOpacity('--chat-reference'), + 'reference-hover': withOpacity('--chat-reference-hover'), + 'reference-selected': withOpacity('--chat-reference-selected'), + 'reference-selected-border': withOpacity('--chat-reference-selected-border') } }, fontSize: { @@ -166,4 +169,4 @@ const config = { ], } satisfies Config -export default config \ No newline at end of file +export default config diff --git a/packages/web/tailwindConfig.test.ts b/packages/web/tailwindConfig.test.ts new file mode 100644 index 000000000..316a9c63a --- /dev/null +++ b/packages/web/tailwindConfig.test.ts @@ -0,0 +1,37 @@ +import postcss from "postcss"; +import tailwindcss from "tailwindcss"; +import { describe, expect, test } from "vitest"; + +import tailwindConfig from "./tailwind.config"; + +const compileUtilities = async (classes: string) => { + const result = await postcss([ + tailwindcss({ + ...tailwindConfig, + content: [{ raw: `
` }], + }), + ]).process("@tailwind utilities;", { from: undefined }); + + return result.css; +}; + +describe("Tailwind theme colors", () => { + test("applies opacity modifiers to CSS variable colors", async () => { + const css = await compileUtilities( + "text-primary text-primary/80 bg-warning/10 border-border/30", + ); + + expect(css).toContain( + "color: color-mix(in srgb, var(--primary) calc(var(--tw-text-opacity, 1) * 100%), transparent)", + ); + expect(css).toContain( + "color: color-mix(in srgb, var(--primary) calc(0.8 * 100%), transparent)", + ); + expect(css).toContain( + "background-color: color-mix(in srgb, var(--warning) calc(0.1 * 100%), transparent)", + ); + expect(css).toContain( + "border-color: color-mix(in srgb, var(--border) calc(0.3 * 100%), transparent)", + ); + }); +}); From b1e74c147bc257da8811c81e62d282f1c639e7f4 Mon Sep 17 00:00:00 2001 From: divyanshu-iitian <23f2001496@ds.study.iitm.ac.in> Date: Thu, 23 Jul 2026 16:49:35 +0530 Subject: [PATCH 2/3] docs: add opacity fix to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33793889c..c84e6ecd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [5.1.4] - 2026-07-24 ### Fixed +- Applied Tailwind opacity modifiers to CSS-variable theme colors. [#1485](https://github.com/sourcebot-dev/sourcebot/pull/1485) - Upgraded `tar` to `^7.5.20`. [#1474](https://github.com/sourcebot-dev/sourcebot/pull/1474) - [EE] Preserved cached repository permissions when OAuth token refresh fails because of a transient code host outage. [#1481](https://github.com/sourcebot-dev/sourcebot/pull/1481) - [EE] Classified code host permission sync failures by provider context before clearing cached repository permissions. [#1482](https://github.com/sourcebot-dev/sourcebot/pull/1482) From 63cdf1f63607291643d8b2787888a02ddb228a7e Mon Sep 17 00:00:00 2001 From: divyanshu-iitian <23f2001496@ds.study.iitm.ac.in> Date: Thu, 23 Jul 2026 17:45:53 +0530 Subject: [PATCH 3/3] fix(web): resolve theme colors for runtime consumers --- CHANGELOG.md | 4 +++- packages/web/src/tailwind.ts | 37 +++++++++++++++++++++++++++-- packages/web/tailwindConfig.test.ts | 13 ++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c84e6ecd6..2083ee927 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- Applied Tailwind opacity modifiers to CSS-variable theme colors. [#1485](https://github.com/sourcebot-dev/sourcebot/pull/1485) + ## [5.1.4] - 2026-07-24 ### Fixed -- Applied Tailwind opacity modifiers to CSS-variable theme colors. [#1485](https://github.com/sourcebot-dev/sourcebot/pull/1485) - Upgraded `tar` to `^7.5.20`. [#1474](https://github.com/sourcebot-dev/sourcebot/pull/1474) - [EE] Preserved cached repository permissions when OAuth token refresh fails because of a transient code host outage. [#1481](https://github.com/sourcebot-dev/sourcebot/pull/1481) - [EE] Classified code host permission sync failures by provider context before clearing cached repository permissions. [#1482](https://github.com/sourcebot-dev/sourcebot/pull/1482) diff --git a/packages/web/src/tailwind.ts b/packages/web/src/tailwind.ts index aafe77704..eb159cc24 100644 --- a/packages/web/src/tailwind.ts +++ b/packages/web/src/tailwind.ts @@ -1,5 +1,38 @@ import resolveConfig from 'tailwindcss/resolveConfig'; import tailwindConfig from '../tailwind.config'; -const tailwind = resolveConfig(tailwindConfig); -export default tailwind; \ No newline at end of file +// Tailwind substitutes this placeholder only while generating utilities. +// Runtime consumers need a concrete opaque value instead. +const resolveRuntimeAlphaValues = (value: T): T => { + if (typeof value === 'string') { + return value + .replaceAll('calc( * 100%)', '100%') + .replaceAll('', '1') as T; + } + + if (Array.isArray(value)) { + return value.map(resolveRuntimeAlphaValues) as T; + } + + if (value !== null && typeof value === 'object') { + return Object.fromEntries( + Object.entries(value).map(([key, child]) => [ + key, + resolveRuntimeAlphaValues(child), + ]), + ) as T; + } + + return value; +}; + +const resolvedConfig = resolveConfig(tailwindConfig); +const tailwind = { + ...resolvedConfig, + theme: { + ...resolvedConfig.theme, + colors: resolveRuntimeAlphaValues(resolvedConfig.theme.colors), + }, +}; + +export default tailwind; diff --git a/packages/web/tailwindConfig.test.ts b/packages/web/tailwindConfig.test.ts index 316a9c63a..8c45b6175 100644 --- a/packages/web/tailwindConfig.test.ts +++ b/packages/web/tailwindConfig.test.ts @@ -2,6 +2,7 @@ import postcss from "postcss"; import tailwindcss from "tailwindcss"; import { describe, expect, test } from "vitest"; +import tailwind from "./src/tailwind"; import tailwindConfig from "./tailwind.config"; const compileUtilities = async (classes: string) => { @@ -34,4 +35,16 @@ describe("Tailwind theme colors", () => { "border-color: color-mix(in srgb, var(--border) calc(0.3 * 100%), transparent)", ); }); + + test("resolves valid opaque colors for runtime consumers", () => { + expect(tailwind.theme.colors.background).toBe( + "color-mix(in srgb, var(--background) 100%, transparent)", + ); + expect(tailwind.theme.colors.editor.background).toBe( + "color-mix(in srgb, var(--editor-background) 100%, transparent)", + ); + expect(JSON.stringify(tailwind.theme.colors)).not.toContain( + "", + ); + }); });