Explorar o código

Remove tmux config schema and add getContrastForeground utility

Alvin Unreal hai 2 semanas
pai
achega
a54b8186d3
Modificáronse 4 ficheiros con 123 adicións e 45 borrados
  1. 2 30
      oh-my-opencode-slim.schema.json
  2. 47 0
      src/tui.test.ts
  3. 68 4
      src/tui.ts
  4. 6 11
      src/utils/env.test.ts

+ 2 - 30
oh-my-opencode-slim.schema.json

@@ -973,32 +973,6 @@
         }
       }
     },
-    "tmux": {
-      "type": "object",
-      "properties": {
-        "enabled": {
-          "default": false,
-          "type": "boolean"
-        },
-        "layout": {
-          "default": "main-vertical",
-          "type": "string",
-          "enum": [
-            "main-horizontal",
-            "main-vertical",
-            "tiled",
-            "even-horizontal",
-            "even-vertical"
-          ]
-        },
-        "main_pane_size": {
-          "default": 60,
-          "type": "number",
-          "minimum": 20,
-          "maximum": 80
-        }
-      }
-    },
     "websearch": {
       "type": "object",
       "properties": {
@@ -1127,14 +1101,12 @@
         "default_preset": {
           "default": "default",
           "type": "string"
-        },
-        "master": {
-          "description": "DEPRECATED - ignored. Council agent synthesizes directly."
         }
       },
       "required": [
         "presets"
-      ]
+      ],
+      "additionalProperties": {}
     },
     "companion": {
       "type": "object",

+ 47 - 0
src/tui.test.ts

@@ -2,7 +2,9 @@ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
 import * as fs from 'node:fs';
 import * as os from 'node:os';
 import * as path from 'node:path';
+import { RGBA } from '@opentui/core';
 import {
+  getContrastForeground,
   getSidebarAgentNames,
   readCompactSidebar,
   readConfigInvalid,
@@ -196,3 +198,48 @@ describe('tui plugin env disable', () => {
     expect(renderRequested).toBe(false);
   });
 });
+
+describe('getContrastForeground', () => {
+  const white = RGBA.fromInts(255, 255, 255);
+  const black = RGBA.fromInts(0, 0, 0);
+  const darkGray = RGBA.fromInts(30, 30, 30);
+  const transparent = RGBA.fromInts(0, 0, 0, 0);
+
+  test('returns theme text when fallback is triggered', () => {
+    expect(getContrastForeground(undefined, 'theme-text', 'theme-bg')).toBe(
+      'theme-text',
+    );
+  });
+
+  test('returns black on a light background', () => {
+    // White background -> black text
+    const result = getContrastForeground(white, white, black) as RGBA;
+    expect(result.toInts()).toEqual([0, 0, 0, 255]);
+  });
+
+  test('returns white on a dark background', () => {
+    // Black background -> white text
+    const result = getContrastForeground(black, white, black) as RGBA;
+    expect(result.toInts()).toEqual([255, 255, 255, 255]);
+  });
+
+  test('respects themeBackground if it is dark and solid when accent is light', () => {
+    const result = getContrastForeground(white, white, darkGray) as RGBA;
+    expect(result.toInts()).toEqual([30, 30, 30, 255]);
+  });
+
+  test('never returns transparent themeBackground even if accent is light', () => {
+    const result = getContrastForeground(white, white, transparent) as RGBA;
+    expect(result.toInts()).toEqual([0, 0, 0, 255]);
+  });
+
+  test('respects themeText if it is light when accent is dark', () => {
+    const result = getContrastForeground(black, white, black) as RGBA;
+    expect(result.toInts()).toEqual([255, 255, 255, 255]);
+  });
+
+  test('parses hex string colors correctly', () => {
+    const result = getContrastForeground('#ffffff', '#ffffff', '#1e1e1e');
+    expect(result).toBe('#1e1e1e');
+  });
+});

+ 68 - 4
src/tui.ts

@@ -1,4 +1,5 @@
 import type { TuiPluginModule } from '@opencode-ai/plugin/tui';
+import { type ColorInput, parseColor, RGBA } from '@opentui/core';
 import type { JSX } from '@opentui/solid';
 import { createElement, insert, setProp } from '@opentui/solid';
 import { DEFAULT_DISABLED_AGENTS, SUBAGENT_NAMES } from './config/constants';
@@ -141,6 +142,61 @@ function compactAgentRow(
   );
 }
 
+export function getContrastForeground(
+  accent: unknown,
+  themeText: unknown,
+  themeBackground: unknown,
+): unknown {
+  if (!accent) return themeText;
+
+  let accentRgba: RGBA;
+  try {
+    accentRgba = parseColor(accent as ColorInput);
+  } catch {
+    return themeText;
+  }
+
+  // Calculate relative luminance: R, G, B are in range 0..1
+  const luminance =
+    0.299 * accentRgba.r + 0.587 * accentRgba.g + 0.114 * accentRgba.b;
+
+  if (luminance > 0.5) {
+    // Light accent bg -> we need a dark fg.
+    // Let's use themeBackground if it exists, is resolved, and not transparent.
+    if (themeBackground) {
+      try {
+        const bgRgba = parseColor(themeBackground as ColorInput);
+        if (bgRgba.a !== 0) {
+          const bgLum = 0.299 * bgRgba.r + 0.587 * bgRgba.g + 0.114 * bgRgba.b;
+          if (bgLum < 0.5) {
+            return themeBackground;
+          }
+        }
+      } catch {
+        // ignore and fallback
+      }
+    }
+    return RGBA.fromInts(0, 0, 0);
+  }
+
+  // Dark accent bg -> we need a light fg.
+  // Let's use themeText if it exists and is light.
+  if (themeText) {
+    try {
+      const textRgba = parseColor(themeText as ColorInput);
+      const textLum =
+        0.299 * textRgba.r + 0.587 * textRgba.g + 0.114 * textRgba.b;
+      if (textLum > 0.5) {
+        return themeText;
+      }
+    } catch {
+      // ignore and fallback
+    }
+  }
+
+  return RGBA.fromInts(255, 255, 255);
+}
+
 function renderSidebar(
   snapshot: TuiSnapshot,
   version: string,
@@ -177,10 +233,18 @@ function renderSidebar(
         [
           box(
             { paddingLeft: 1, paddingRight: 1, backgroundColor: theme.accent },
-            // Use theme.text, not theme.background: when the theme background is
-            // "none" (transparent) the foreground becomes RGBA(0,0,0,0) and the
-            // badge text vanishes. See #582.
-            [text({ fg: theme.text }, ['OMO-Slim'])],
+            [
+              text(
+                {
+                  fg: getContrastForeground(
+                    theme.accent,
+                    theme.text,
+                    theme.background,
+                  ),
+                },
+                ['OMO-Slim'],
+              ),
+            ],
           ),
           text({ fg: theme.textMuted }, [`v${version}`]),
         ],

+ 6 - 11
src/utils/env.test.ts

@@ -10,17 +10,12 @@ describe('isTruthyEnvValue', () => {
     expect(isTruthyEnvValue(value)).toBe(true);
   });
 
-  test.each([
-    undefined,
-    '',
-    '0',
-    'false',
-    'no',
-    'off',
-    'anything',
-  ])('%p is not truthy', (value) => {
-    expect(isTruthyEnvValue(value)).toBe(false);
-  });
+  test.each([undefined, '', '0', 'false', 'no', 'off', 'anything'])(
+    '%p is not truthy',
+    (value) => {
+      expect(isTruthyEnvValue(value)).toBe(false);
+    },
+  );
 });
 
 describe('isPluginDisabledByEnv', () => {