From 12dc50f8facd2a611a80a19a6f3419bf79151fb7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:18:23 +0000 Subject: [PATCH 01/15] Initial plan From 7b611c08c8627428c0f8549bf10109dc65c47f53 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:22:53 +0000 Subject: [PATCH 02/15] Add elegant beige theme matching logo style Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- package-lock.json | 9 +++ package.json | 6 ++ src/resolver.ts | 3 +- src/settings.ts | 10 ++- src/tests/logicRegression.test.ts | 1 + src/web/WebConflictPanel.ts | 1 + src/web/client/App.tsx | 10 ++- src/web/client/styles.ts | 109 +++++++++++++++++++++++------- src/web/client/types.ts | 1 + src/web/webTypes.ts | 5 ++ 10 files changed, 127 insertions(+), 28 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea49609..ff54ed0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -984,6 +984,7 @@ "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -994,6 +995,7 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1077,6 +1079,7 @@ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", @@ -1318,6 +1321,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2304,6 +2308,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4725,6 +4730,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4734,6 +4740,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5554,6 +5561,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5738,6 +5746,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 2e06b24..20eb7b4 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,12 @@ "type": "boolean", "default": false, "description": "Show the base (common ancestor) branch column in the conflict resolution view. When false, only current and incoming columns are shown." + }, + "mergeNB.ui.theme": { + "type": "string", + "enum": ["dark", "elegant"], + "default": "elegant", + "description": "Visual theme for the conflict resolution UI. 'elegant' provides a light, beige theme inspired by the MergeNB logo. 'dark' provides the traditional dark theme." } } } diff --git a/src/resolver.ts b/src/resolver.ts index d67ccc4..72a5d14 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -272,7 +272,8 @@ export class NotebookConflictResolver { autoResolveResult: autoResolveResult, hideNonConflictOutputs: settings.hideNonConflictOutputs, enableUndoRedoHotkeys: settings.enableUndoRedoHotkeys, - showBaseColumn: settings.showBaseColumn + showBaseColumn: settings.showBaseColumn, + theme: settings.theme }; const resolutionCallback = async (resolution: UnifiedResolution): Promise => { diff --git a/src/settings.ts b/src/settings.ts index 828aa1e..d749f60 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -10,6 +10,7 @@ * - hideNonConflictOutputs: Hide outputs for non-conflicted cells in UI (default: true) * - enableUndoRedoHotkeys: Enable Ctrl+Z / Ctrl+Shift+Z in web UI (default: true) * - showBaseColumn: Show base branch column in 3-column view (default: false, true in headless/testing) + * - theme: UI theme selection ('dark' | 'elegant', default: 'elegant') * * These reduce manual conflict resolution for common non-semantic differences. */ @@ -30,6 +31,7 @@ export interface MergeNBSettings { hideNonConflictOutputs: boolean; enableUndoRedoHotkeys: boolean; showBaseColumn: boolean; + theme: 'dark' | 'elegant'; } /** Default settings used in headless mode */ @@ -40,7 +42,8 @@ const DEFAULT_SETTINGS: MergeNBSettings = { autoResolveWhitespace: true, hideNonConflictOutputs: true, enableUndoRedoHotkeys: true, - showBaseColumn: true + showBaseColumn: true, + theme: 'elegant' }; /** @@ -64,6 +67,7 @@ export function getSettings(): MergeNBSettings { hideNonConflictOutputs: true, enableUndoRedoHotkeys: true, showBaseColumn: false, + theme: 'elegant', }; const config = vscode.workspace.getConfiguration('mergeNB'); @@ -76,6 +80,7 @@ export function getSettings(): MergeNBSettings { hideNonConflictOutputs: config.get('ui.hideNonConflictOutputs', defaults.hideNonConflictOutputs), enableUndoRedoHotkeys: config.get('ui.enableUndoRedoHotkeys', defaults.enableUndoRedoHotkeys), showBaseColumn: config.get('ui.showBaseColumn', defaults.showBaseColumn), + theme: config.get<'dark' | 'elegant'>('ui.theme', defaults.theme), }; } @@ -84,5 +89,6 @@ export function getSettings(): MergeNBSettings { */ export function isAutoResolveEnabled(setting: keyof MergeNBSettings): boolean { const settings = getSettings(); - return settings[setting]; + const value = settings[setting]; + return typeof value === 'boolean' ? value : false; } diff --git a/src/tests/logicRegression.test.ts b/src/tests/logicRegression.test.ts index c93d627..37c9354 100644 --- a/src/tests/logicRegression.test.ts +++ b/src/tests/logicRegression.test.ts @@ -68,6 +68,7 @@ export async function run(): Promise { hideNonConflictOutputs: true, enableUndoRedoHotkeys: true, showBaseColumn: true, + theme: 'elegant', }); assert.ok( conflicts.some(c => c.type === 'metadata-changed'), diff --git a/src/web/WebConflictPanel.ts b/src/web/WebConflictPanel.ts index df96acc..048de6e 100644 --- a/src/web/WebConflictPanel.ts +++ b/src/web/WebConflictPanel.ts @@ -120,6 +120,7 @@ export class WebConflictPanel { hideNonConflictOutputs: this._conflict.hideNonConflictOutputs, enableUndoRedoHotkeys: this._conflict.enableUndoRedoHotkeys, showBaseColumn: this._conflict.showBaseColumn, + theme: this._conflict.theme, currentBranch: this._conflict.semanticConflict?.currentBranch, incomingBranch: this._conflict.semanticConflict?.incomingBranch, }; diff --git a/src/web/client/App.tsx b/src/web/client/App.tsx index 96e33df..1078d64 100644 --- a/src/web/client/App.tsx +++ b/src/web/client/App.tsx @@ -3,14 +3,22 @@ * @description Root React component for the conflict resolver. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { useWebSocket } from './useWebSocket'; import { ConflictResolver } from './ConflictResolver'; +import { injectStyles } from './styles'; import type { ConflictChoice, ResolvedRow } from './types'; export function App(): React.ReactElement { const { connected, conflictData, sendMessage, resolutionStatus, resolutionMessage } = useWebSocket(); + // Inject theme styles when conflict data loads + useEffect(() => { + if (conflictData?.theme) { + injectStyles(conflictData.theme); + } + }, [conflictData?.theme]); + const handleResolve = (resolutions: ConflictChoice[], markAsResolved: boolean, renumberExecutionCounts: boolean, resolvedRows: ResolvedRow[]) => { sendMessage({ command: 'resolve', diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index 2be9eb6..5dab3f5 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -3,27 +3,79 @@ * @description Shared styles for the conflict resolver UI. */ -export const styles = ` +export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { + const isDark = theme === 'dark'; + + // Color palette based on theme + const colors = isDark ? { + bgPrimary: '#1e1e1e', + bgSecondary: '#252526', + bgTertiary: '#2d2d2d', + borderColor: '#3c3c3c', + textPrimary: '#f3f3f3', + textSecondary: '#808080', + accentBlue: '#007acc', + accentGreen: '#4ec9b0', + currentBg: 'rgba(64, 164, 223, 0.15)', + currentBorder: '#40a4df', + currentRgb: '64, 164, 223', + incomingBg: 'rgba(78, 201, 176, 0.15)', + incomingBorder: '#4ec9b0', + incomingRgb: '78, 201, 176', + baseBg: 'rgba(128, 128, 128, 0.15)', + baseBorder: '#808080', + diffAdd: 'rgba(78, 201, 176, 0.3)', + diffRemove: 'rgba(244, 135, 113, 0.3)', + diffChange: 'rgba(255, 213, 79, 0.3)', + bodyBackground: '#1e1e1e', + bodyBackgroundImage: 'none', + } : { + // Elegant theme - inspired by MergeNB logo + bgPrimary: '#ffffff', + bgSecondary: '#f5f2ec', + bgTertiary: '#ebe7df', + borderColor: 'rgba(0, 0, 0, 0.1)', + textPrimary: '#1A202C', + textSecondary: '#6B7280', + accentBlue: '#569cd6', + accentGreen: '#4ec9b0', + currentBg: 'rgba(164, 212, 222, 0.25)', + currentBorder: '#A4D4DE', + currentRgb: '164, 212, 222', + incomingBg: 'rgba(195, 201, 242, 0.25)', + incomingBorder: '#C3C9F2', + incomingRgb: '195, 201, 242', + baseBg: 'rgba(128, 128, 128, 0.12)', + baseBorder: '#999999', + diffAdd: 'rgba(195, 201, 242, 0.4)', + diffRemove: 'rgba(244, 135, 113, 0.35)', + diffChange: 'rgba(255, 193, 7, 0.35)', + bodyBackground: '#F9F7F1', + bodyBackgroundImage: `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), + linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`, + }; + + return ` :root { - --bg-primary: #1e1e1e; - --bg-secondary: #252526; - --bg-tertiary: #2d2d2d; - --border-color: #3c3c3c; - --text-primary: #f3f3f3; - --text-secondary: #808080; - --accent-blue: #007acc; - --accent-green: #4ec9b0; - --current-bg: rgba(64, 164, 223, 0.15); - --current-border: #40a4df; - --current-rgb: 64, 164, 223; - --incoming-bg: rgba(78, 201, 176, 0.15); - --incoming-border: #4ec9b0; - --incoming-rgb: 78, 201, 176; - --base-bg: rgba(128, 128, 128, 0.15); - --base-border: #808080; - --diff-add: rgba(78, 201, 176, 0.3); - --diff-remove: rgba(244, 135, 113, 0.3); - --diff-change: rgba(255, 213, 79, 0.3); + --bg-primary: ${colors.bgPrimary}; + --bg-secondary: ${colors.bgSecondary}; + --bg-tertiary: ${colors.bgTertiary}; + --border-color: ${colors.borderColor}; + --text-primary: ${colors.textPrimary}; + --text-secondary: ${colors.textSecondary}; + --accent-blue: ${colors.accentBlue}; + --accent-green: ${colors.accentGreen}; + --current-bg: ${colors.currentBg}; + --current-border: ${colors.currentBorder}; + --current-rgb: ${colors.currentRgb}; + --incoming-bg: ${colors.incomingBg}; + --incoming-border: ${colors.incomingBorder}; + --incoming-rgb: ${colors.incomingRgb}; + --base-bg: ${colors.baseBg}; + --base-border: ${colors.baseBorder}; + --diff-add: ${colors.diffAdd}; + --diff-remove: ${colors.diffRemove}; + --diff-change: ${colors.diffChange}; } * { @@ -34,7 +86,9 @@ export const styles = ` body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - background: var(--bg-primary); + background: ${colors.bodyBackground}; + ${colors.bodyBackgroundImage !== 'none' ? `background-image: ${colors.bodyBackgroundImage};` : ''} + ${colors.bodyBackgroundImage !== 'none' ? 'background-size: 20px 20px;' : ''} color: var(--text-primary); line-height: 1.5; } @@ -1022,15 +1076,22 @@ body { color: var(--text-primary); } `; +} -export function injectStyles(): void { +export function injectStyles(theme: 'dark' | 'elegant' = 'elegant'): void { if (typeof document !== 'undefined') { const existing = document.getElementById('mergenb-styles'); - if (existing) return; + if (existing) { + existing.textContent = getStyles(theme); + return; + } const style = document.createElement('style'); style.id = 'mergenb-styles'; - style.textContent = styles; + style.textContent = getStyles(theme); document.head.appendChild(style); } } + +// Keep backward compatibility +export const styles = getStyles('elegant'); diff --git a/src/web/client/types.ts b/src/web/client/types.ts index ef3071d..4cffb29 100644 --- a/src/web/client/types.ts +++ b/src/web/client/types.ts @@ -56,6 +56,7 @@ export interface UnifiedConflictData { incomingBranch?: string; enableUndoRedoHotkeys?: boolean; showBaseColumn?: boolean; + theme?: 'dark' | 'elegant'; } /** diff --git a/src/web/webTypes.ts b/src/web/webTypes.ts index ec4c24d..884a11f 100644 --- a/src/web/webTypes.ts +++ b/src/web/webTypes.ts @@ -33,6 +33,8 @@ export interface UnifiedConflict { enableUndoRedoHotkeys?: boolean; /** Whether to show the base column in the 3-way merge view */ showBaseColumn?: boolean; + /** UI theme ('dark' | 'elegant') */ + theme?: 'dark' | 'elegant'; } /** @@ -99,6 +101,9 @@ export interface WebConflictData { // Branch information currentBranch?: string; incomingBranch?: string; + + // UI theme + theme?: 'dark' | 'elegant'; } /** From 222bb577075efa3daca3a09a995bd9ec39dde188 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:26:18 +0000 Subject: [PATCH 03/15] Fix code style: improve indentation in styles.ts Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- package-lock.json | 9 --------- src/web/client/styles.ts | 2 +- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index ff54ed0..ea49609 100644 --- a/package-lock.json +++ b/package-lock.json @@ -984,7 +984,6 @@ "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -995,7 +994,6 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1079,7 +1077,6 @@ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", @@ -1321,7 +1318,6 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2308,7 +2304,6 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4730,7 +4725,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -4740,7 +4734,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5561,7 +5554,6 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -5746,7 +5738,6 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", - "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index 5dab3f5..f444b9f 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -52,7 +52,7 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { diffChange: 'rgba(255, 193, 7, 0.35)', bodyBackground: '#F9F7F1', bodyBackgroundImage: `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), - linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`, + linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`, }; return ` From 7b1c726782b58317c03ea98a90d69a1144a0ab44 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:27:41 +0000 Subject: [PATCH 04/15] Address code review: improve type safety and code organization Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/settings.ts | 8 +++++--- src/web/client/styles.ts | 13 +++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/settings.ts b/src/settings.ts index d749f60..4c067b8 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -87,8 +87,10 @@ export function getSettings(): MergeNBSettings { /** * Check if a specific auto-resolve setting is enabled */ -export function isAutoResolveEnabled(setting: keyof MergeNBSettings): boolean { +export function isAutoResolveEnabled( + setting: 'autoResolveExecutionCount' | 'autoResolveKernelVersion' | 'stripOutputs' | + 'autoResolveWhitespace' | 'hideNonConflictOutputs' | 'enableUndoRedoHotkeys' | 'showBaseColumn' +): boolean { const settings = getSettings(); - const value = settings[setting]; - return typeof value === 'boolean' ? value : false; + return settings[setting]; } diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index f444b9f..4f1184f 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -6,6 +6,10 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { const isDark = theme === 'dark'; + // Checkered background gradient for elegant theme + const ELEGANT_GRID_GRADIENT = `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), + linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`; + // Color palette based on theme const colors = isDark ? { bgPrimary: '#1e1e1e', @@ -51,10 +55,11 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { diffRemove: 'rgba(244, 135, 113, 0.35)', diffChange: 'rgba(255, 193, 7, 0.35)', bodyBackground: '#F9F7F1', - bodyBackgroundImage: `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), - linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`, + bodyBackgroundImage: ELEGANT_GRID_GRADIENT, }; + const hasBackgroundImage = colors.bodyBackgroundImage !== 'none'; + return ` :root { --bg-primary: ${colors.bgPrimary}; @@ -87,8 +92,8 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; background: ${colors.bodyBackground}; - ${colors.bodyBackgroundImage !== 'none' ? `background-image: ${colors.bodyBackgroundImage};` : ''} - ${colors.bodyBackgroundImage !== 'none' ? 'background-size: 20px 20px;' : ''} + ${hasBackgroundImage ? `background-image: ${colors.bodyBackgroundImage};` : ''} + ${hasBackgroundImage ? 'background-size: 20px 20px;' : ''} color: var(--text-primary); line-height: 1.5; } From 882fc348e9633dba481363c41d1caa178f4e3f6b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 02:28:43 +0000 Subject: [PATCH 05/15] Final polish: fix indentation and restrict isAutoResolveEnabled scope Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- src/settings.ts | 6 +++--- src/web/client/styles.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/settings.ts b/src/settings.ts index 4c067b8..00e768a 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -85,11 +85,11 @@ export function getSettings(): MergeNBSettings { } /** - * Check if a specific auto-resolve setting is enabled + * Check if a specific auto-resolve setting is enabled. + * Only checks actual auto-resolve settings, not UI settings. */ export function isAutoResolveEnabled( - setting: 'autoResolveExecutionCount' | 'autoResolveKernelVersion' | 'stripOutputs' | - 'autoResolveWhitespace' | 'hideNonConflictOutputs' | 'enableUndoRedoHotkeys' | 'showBaseColumn' + setting: 'autoResolveExecutionCount' | 'autoResolveKernelVersion' | 'stripOutputs' | 'autoResolveWhitespace' ): boolean { const settings = getSettings(); return settings[setting]; diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index 4f1184f..c7f04a1 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -8,7 +8,7 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { // Checkered background gradient for elegant theme const ELEGANT_GRID_GRADIENT = `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), - linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`; + linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`; // Color palette based on theme const colors = isDark ? { From 251d6cba08a79de544c70a2c267d000c85fa2368 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 13 Feb 2026 03:19:11 +0000 Subject: [PATCH 06/15] Add logo icon, make cells transparent, improve conflict visibility Co-authored-by: Avni2000 <77120766+Avni2000@users.noreply.github.com> --- package-lock.json | 9 +++++ src/web/client/ConflictResolver.tsx | 4 ++ src/web/client/styles.ts | 57 +++++++++++++++++++++++------ 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index ea49609..ff54ed0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -984,6 +984,7 @@ "integrity": "sha512-BkmoP5/FhRYek5izySdkOneRyXYN35I860MFAGupTdebyE66uZaR+bXLHq8k4DirE5DwQi3NuhvRU1jqTVwUrQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -994,6 +995,7 @@ "integrity": "sha512-KkiJeU6VbYbUOp5ITMIc7kBfqlYkKA5KhEHVrGMmUUMt7NeaZg65ojdPk+FtNrBAOXNVM5QM72jnADjM+XVRAQ==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.2.2" } @@ -1077,6 +1079,7 @@ "integrity": "sha512-4z2nCSBfVIMnbuu8uinj+f0o4qOeggYJLbjpPHka3KH1om7e+H9yLKTYgksTaHcGco+NClhhY2vyO3HsMH1RGw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.55.0", "@typescript-eslint/types": "8.55.0", @@ -1318,6 +1321,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2304,6 +2308,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4725,6 +4730,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -4734,6 +4740,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5554,6 +5561,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5738,6 +5746,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/web/client/ConflictResolver.tsx b/src/web/client/ConflictResolver.tsx index feace59..a9395a9 100644 --- a/src/web/client/ConflictResolver.tsx +++ b/src/web/client/ConflictResolver.tsx @@ -541,6 +541,10 @@ export function ConflictResolver({
+
+
+
+
MergeNB {fileName}
diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index c7f04a1..05246c8 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -123,6 +123,39 @@ body { gap: 16px; } +/* Logo icon */ +.logo-icon { + position: relative; + width: 40px; + height: 34px; + display: flex; + justify-content: center; + align-items: center; +} + +.logo-card { + position: absolute; + width: 24px; + height: 32px; + border-radius: 4px; + mix-blend-mode: multiply; + opacity: 0.9; + transform-origin: 50% 90%; + top: 0; +} + +.logo-card-left { + background-color: #A4D4DE; + left: 6px; + transform: rotate(-20deg); +} + +.logo-card-right { + background-color: #C3C9F2; + right: 6px; + transform: rotate(20deg); +} + .header-title { font-size: 14px; font-weight: 600; @@ -286,7 +319,7 @@ body { margin-bottom: 12px; position: sticky; top: 0; - background: var(--bg-primary); + background: transparent; padding: 8px 0; z-index: 50; } @@ -318,19 +351,19 @@ body { } .merge-row.conflict-row { - border: 2px solid var(--border-color); - background: var(--bg-secondary); + border: 3px solid rgba(244, 135, 113, 0.6); + background: rgba(244, 135, 113, 0.08); } .merge-row.identical-row { - opacity: 0.7; + /* No opacity reduction - keep text readable */ } .cell-columns { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 1px; - background: var(--border-color); + background: transparent; } .cell-columns.two-column { @@ -338,7 +371,7 @@ body { } .cell-column { - background: var(--bg-primary); + background: transparent; padding: 12px; min-height: 60px; display: flex; @@ -376,7 +409,7 @@ body { .cell-content pre { margin: 0; padding: 12px; - background: var(--bg-tertiary); + background: rgba(255, 255, 255, 0.5); border-radius: 4px; overflow-x: auto; white-space: pre-wrap; @@ -389,7 +422,7 @@ body { .markdown-cell .cell-content { padding: 12px; - background: var(--bg-tertiary); + background: rgba(255, 255, 255, 0.5); border-radius: 4px; border-left: 3px solid var(--accent-green); } @@ -403,7 +436,7 @@ body { color: var(--text-primary); font-style: italic; font-size: 12px; - background: var(--bg-tertiary); + background: rgba(255, 255, 255, 0.3); border-radius: 4px; border: 1px dashed var(--border-color); } @@ -416,7 +449,7 @@ body { .metadata-cell pre { margin: 0; padding: 12px; - background: var(--bg-tertiary); + background: rgba(255, 255, 255, 0.5); border-radius: 4px; overflow-x: auto; white-space: pre-wrap; @@ -520,7 +553,7 @@ body { .cell-outputs { margin-top: 8px; padding: 8px; - background: var(--bg-tertiary); + background: rgba(255, 255, 255, 0.4); border-radius: 4px; font-size: 12px; } @@ -534,7 +567,7 @@ body { color: var(--text-secondary); font-style: italic; padding: 8px; - background: var(--bg-secondary); + background: rgba(255, 255, 255, 0.3); border: 1px dashed var(--border-color); border-radius: 4px; font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace; From 505ad36f0b3073c4ef6b0cff7ab07264f00eb9cb Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Fri, 13 Feb 2026 11:21:12 -0600 Subject: [PATCH 07/15] [FIX] Add logo exactly --- src/web/client/ConflictResolver.tsx | 5 +++- src/web/client/styles.ts | 42 +++++++++++++++++++++-------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/src/web/client/ConflictResolver.tsx b/src/web/client/ConflictResolver.tsx index a9395a9..4c2bafa 100644 --- a/src/web/client/ConflictResolver.tsx +++ b/src/web/client/ConflictResolver.tsx @@ -545,7 +545,10 @@ export function ConflictResolver({
- MergeNB +
+ Merge + NB +
{fileName}
diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index 05246c8..4dbbaa8 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -61,6 +61,8 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { const hasBackgroundImage = colors.bodyBackgroundImage !== 'none'; return ` +@import url('https://fonts.googleapis.com/css2?family=DM+Sans:wght@700&family=Playfair+Display:ital,wght@1,500&display=swap'); + :root { --bg-primary: ${colors.bgPrimary}; --bg-secondary: ${colors.bgSecondary}; @@ -126,8 +128,8 @@ body { /* Logo icon */ .logo-icon { position: relative; - width: 40px; - height: 34px; + width: 60px; + height: 50px; display: flex; justify-content: center; align-items: center; @@ -135,9 +137,9 @@ body { .logo-card { position: absolute; - width: 24px; - height: 32px; - border-radius: 4px; + width: 34px; + height: 48px; + border-radius: 6px; mix-blend-mode: multiply; opacity: 0.9; transform-origin: 50% 90%; @@ -146,19 +148,37 @@ body { .logo-card-left { background-color: #A4D4DE; - left: 6px; - transform: rotate(-20deg); + left: 8px; + transform: rotate(-25deg); } .logo-card-right { background-color: #C3C9F2; - right: 6px; - transform: rotate(20deg); + right: 8px; + transform: rotate(25deg); } .header-title { - font-size: 14px; - font-weight: 600; + display: flex; + align-items: baseline; + gap: 0; + color: var(--text-primary); + line-height: 1; + letter-spacing: -0.03em; +} + +.header-title-merge { + font-family: 'Playfair Display', serif; + font-style: italic; + font-weight: 500; + font-size: 24px; + letter-spacing: -0.02em; +} + +.header-title-nb { + font-family: 'DM Sans', sans-serif; + font-weight: 700; + font-size: 24px; } .file-path { From f34cc669283fb5a79d34602ab83db03fe4a9a013 Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Fri, 13 Feb 2026 11:30:23 -0600 Subject: [PATCH 08/15] [FIX] Lower contrast for light theme - rename elegant -> light --- src/settings.ts | 10 +++---- src/tests/logicRegression.test.ts | 2 +- src/web/client/styles.ts | 46 +++++++++++++++++++------------ src/web/client/types.ts | 2 +- src/web/webTypes.ts | 6 ++-- 5 files changed, 39 insertions(+), 27 deletions(-) diff --git a/src/settings.ts b/src/settings.ts index 00e768a..fa5c23f 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -10,7 +10,7 @@ * - hideNonConflictOutputs: Hide outputs for non-conflicted cells in UI (default: true) * - enableUndoRedoHotkeys: Enable Ctrl+Z / Ctrl+Shift+Z in web UI (default: true) * - showBaseColumn: Show base branch column in 3-column view (default: false, true in headless/testing) - * - theme: UI theme selection ('dark' | 'elegant', default: 'elegant') + * - theme: UI theme selection ('dark' | 'light', default: 'light') * * These reduce manual conflict resolution for common non-semantic differences. */ @@ -31,7 +31,7 @@ export interface MergeNBSettings { hideNonConflictOutputs: boolean; enableUndoRedoHotkeys: boolean; showBaseColumn: boolean; - theme: 'dark' | 'elegant'; + theme: 'dark' | 'light'; } /** Default settings used in headless mode */ @@ -43,7 +43,7 @@ const DEFAULT_SETTINGS: MergeNBSettings = { hideNonConflictOutputs: true, enableUndoRedoHotkeys: true, showBaseColumn: true, - theme: 'elegant' + theme: 'light' }; /** @@ -67,7 +67,7 @@ export function getSettings(): MergeNBSettings { hideNonConflictOutputs: true, enableUndoRedoHotkeys: true, showBaseColumn: false, - theme: 'elegant', + theme: 'light', }; const config = vscode.workspace.getConfiguration('mergeNB'); @@ -80,7 +80,7 @@ export function getSettings(): MergeNBSettings { hideNonConflictOutputs: config.get('ui.hideNonConflictOutputs', defaults.hideNonConflictOutputs), enableUndoRedoHotkeys: config.get('ui.enableUndoRedoHotkeys', defaults.enableUndoRedoHotkeys), showBaseColumn: config.get('ui.showBaseColumn', defaults.showBaseColumn), - theme: config.get<'dark' | 'elegant'>('ui.theme', defaults.theme), + theme: config.get<'dark' | 'light'>('ui.theme', defaults.theme), }; } diff --git a/src/tests/logicRegression.test.ts b/src/tests/logicRegression.test.ts index 37c9354..ee11091 100644 --- a/src/tests/logicRegression.test.ts +++ b/src/tests/logicRegression.test.ts @@ -68,7 +68,7 @@ export async function run(): Promise { hideNonConflictOutputs: true, enableUndoRedoHotkeys: true, showBaseColumn: true, - theme: 'elegant', + theme: 'light', }); assert.ok( conflicts.some(c => c.type === 'metadata-changed'), diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index 4dbbaa8..e2b8773 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -3,11 +3,11 @@ * @description Shared styles for the conflict resolver UI. */ -export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { +export function getStyles(theme: 'dark' | 'light' = 'light'): string { const isDark = theme === 'dark'; - // Checkered background gradient for elegant theme - const ELEGANT_GRID_GRADIENT = `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), + // Checkered background gradient for light theme + const LIGHT_GRID_GRADIENT = `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`; // Color palette based on theme @@ -31,13 +31,17 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { diffAdd: 'rgba(78, 201, 176, 0.3)', diffRemove: 'rgba(244, 135, 113, 0.3)', diffChange: 'rgba(255, 213, 79, 0.3)', + cellSurface: 'rgba(255, 255, 255, 0.08)', + cellSurfaceSoft: 'rgba(255, 255, 255, 0.05)', + cellPlaceholderBg: 'rgba(255, 255, 255, 0.04)', + outputBg: 'rgba(255, 255, 255, 0.06)', bodyBackground: '#1e1e1e', bodyBackgroundImage: 'none', } : { - // Elegant theme - inspired by MergeNB logo - bgPrimary: '#ffffff', - bgSecondary: '#f5f2ec', - bgTertiary: '#ebe7df', + // LIGHT theme - inspired by MergeNB logo + bgPrimary: '#f1ece3', + bgSecondary: '#ebe3d8', + bgTertiary: '#e2d8ca', borderColor: 'rgba(0, 0, 0, 0.1)', textPrimary: '#1A202C', textSecondary: '#6B7280', @@ -54,8 +58,12 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { diffAdd: 'rgba(195, 201, 242, 0.4)', diffRemove: 'rgba(244, 135, 113, 0.35)', diffChange: 'rgba(255, 193, 7, 0.35)', - bodyBackground: '#F9F7F1', - bodyBackgroundImage: ELEGANT_GRID_GRADIENT, + cellSurface: 'rgba(226, 216, 202, 0.78)', + cellSurfaceSoft: 'rgba(226, 216, 202, 0.62)', + cellPlaceholderBg: 'rgba(226, 216, 202, 0.48)', + outputBg: 'rgba(226, 216, 202, 0.66)', + bodyBackground: '#EAE2D5', + bodyBackgroundImage: LIGHT_GRID_GRADIENT, }; const hasBackgroundImage = colors.bodyBackgroundImage !== 'none'; @@ -83,6 +91,10 @@ export function getStyles(theme: 'dark' | 'elegant' = 'elegant'): string { --diff-add: ${colors.diffAdd}; --diff-remove: ${colors.diffRemove}; --diff-change: ${colors.diffChange}; + --cell-surface: ${colors.cellSurface}; + --cell-surface-soft: ${colors.cellSurfaceSoft}; + --cell-placeholder-bg: ${colors.cellPlaceholderBg}; + --output-bg: ${colors.outputBg}; } * { @@ -429,7 +441,7 @@ body { .cell-content pre { margin: 0; padding: 12px; - background: rgba(255, 255, 255, 0.5); + background: var(--cell-surface); border-radius: 4px; overflow-x: auto; white-space: pre-wrap; @@ -442,7 +454,7 @@ body { .markdown-cell .cell-content { padding: 12px; - background: rgba(255, 255, 255, 0.5); + background: var(--cell-surface); border-radius: 4px; border-left: 3px solid var(--accent-green); } @@ -456,7 +468,7 @@ body { color: var(--text-primary); font-style: italic; font-size: 12px; - background: rgba(255, 255, 255, 0.3); + background: var(--cell-placeholder-bg); border-radius: 4px; border: 1px dashed var(--border-color); } @@ -469,7 +481,7 @@ body { .metadata-cell pre { margin: 0; padding: 12px; - background: rgba(255, 255, 255, 0.5); + background: var(--cell-surface); border-radius: 4px; overflow-x: auto; white-space: pre-wrap; @@ -573,7 +585,7 @@ body { .cell-outputs { margin-top: 8px; padding: 8px; - background: rgba(255, 255, 255, 0.4); + background: var(--output-bg); border-radius: 4px; font-size: 12px; } @@ -587,7 +599,7 @@ body { color: var(--text-secondary); font-style: italic; padding: 8px; - background: rgba(255, 255, 255, 0.3); + background: var(--cell-surface-soft); border: 1px dashed var(--border-color); border-radius: 4px; font-family: "SF Mono", Monaco, "Cascadia Code", "Courier New", monospace; @@ -1136,7 +1148,7 @@ body { `; } -export function injectStyles(theme: 'dark' | 'elegant' = 'elegant'): void { +export function injectStyles(theme: 'dark' | 'light' = 'light'): void { if (typeof document !== 'undefined') { const existing = document.getElementById('mergenb-styles'); if (existing) { @@ -1152,4 +1164,4 @@ export function injectStyles(theme: 'dark' | 'elegant' = 'elegant'): void { } // Keep backward compatibility -export const styles = getStyles('elegant'); +export const styles = getStyles('light'); diff --git a/src/web/client/types.ts b/src/web/client/types.ts index 4cffb29..e53cb2f 100644 --- a/src/web/client/types.ts +++ b/src/web/client/types.ts @@ -56,7 +56,7 @@ export interface UnifiedConflictData { incomingBranch?: string; enableUndoRedoHotkeys?: boolean; showBaseColumn?: boolean; - theme?: 'dark' | 'elegant'; + theme?: 'dark' | 'light'; } /** diff --git a/src/web/webTypes.ts b/src/web/webTypes.ts index 884a11f..c4ce67c 100644 --- a/src/web/webTypes.ts +++ b/src/web/webTypes.ts @@ -33,8 +33,8 @@ export interface UnifiedConflict { enableUndoRedoHotkeys?: boolean; /** Whether to show the base column in the 3-way merge view */ showBaseColumn?: boolean; - /** UI theme ('dark' | 'elegant') */ - theme?: 'dark' | 'elegant'; + /** UI theme ('dark' | 'light') */ + theme?: 'dark' | 'light'; } /** @@ -103,7 +103,7 @@ export interface WebConflictData { incomingBranch?: string; // UI theme - theme?: 'dark' | 'elegant'; + theme?: 'dark' | 'light'; } /** From 164a7ffc3e0de11b5b657f55e18a4e07df9c508d Mon Sep 17 00:00:00 2001 From: Avni2000 Date: Fri, 13 Feb 2026 12:07:58 -0600 Subject: [PATCH 09/15] [FIX] Light and dark themes properly switch loading screens - Logo changes - Textarea fixes - Align dark with light as a reference point --- package.json | 6 +- src/web/WebConflictPanel.ts | 3 +- src/web/client/index.tsx | 14 ++++- src/web/client/styles.ts | 107 ++++++++++++++++++++---------------- src/web/webServer.ts | 26 ++++++--- 5 files changed, 97 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index 20eb7b4..bd243bb 100644 --- a/package.json +++ b/package.json @@ -115,9 +115,9 @@ }, "mergeNB.ui.theme": { "type": "string", - "enum": ["dark", "elegant"], - "default": "elegant", - "description": "Visual theme for the conflict resolution UI. 'elegant' provides a light, beige theme inspired by the MergeNB logo. 'dark' provides the traditional dark theme." + "enum": ["dark", "light"], + "default": "light", + "description": "Visual theme for the conflict resolution UI. 'light' provides a light, beige theme inspired by the MergeNB logo. 'dark' provides an inverse dark theme." } } } diff --git a/src/web/WebConflictPanel.ts b/src/web/WebConflictPanel.ts index 048de6e..c102f06 100644 --- a/src/web/WebConflictPanel.ts +++ b/src/web/WebConflictPanel.ts @@ -92,7 +92,8 @@ export class WebConflictPanel { void server.openSession( this._sessionId, '', // No HTML content needed - server generates shell - (message: unknown) => this._handleMessage(message) + (message: unknown) => this._handleMessage(message), + this._conflict?.theme ?? 'light' ).then(() => { // Send conflict data to browser once connected this._sendConflictData(); diff --git a/src/web/client/index.tsx b/src/web/client/index.tsx index fe375f4..30e09c4 100644 --- a/src/web/client/index.tsx +++ b/src/web/client/index.tsx @@ -8,8 +8,18 @@ import { createRoot } from 'react-dom/client'; import { App } from './App'; import { injectStyles } from './styles'; -// Inject styles into the document -injectStyles(); +declare global { + interface Window { + __MERGENB_INITIAL_THEME?: 'dark' | 'light'; + } +} + +// Use server-provided theme first so loading and app boot with the same palette. +const initialTheme = + window.__MERGENB_INITIAL_THEME ?? + (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'); + +injectStyles(initialTheme); // Mount the React app const container = document.getElementById('root'); diff --git a/src/web/client/styles.ts b/src/web/client/styles.ts index e2b8773..27c1def 100644 --- a/src/web/client/styles.ts +++ b/src/web/client/styles.ts @@ -6,37 +6,44 @@ export function getStyles(theme: 'dark' | 'light' = 'light'): string { const isDark = theme === 'dark'; + // Checkered background gradients + const DARK_GRID_GRADIENT = `linear-gradient(to right, rgba(255,255,255,0.04) 1px, transparent 1px), + linear-gradient(to bottom, rgba(255,255,255,0.04) 1px, transparent 1px)`; + // Checkered background gradient for light theme const LIGHT_GRID_GRADIENT = `linear-gradient(to right, rgba(0,0,0,0.05) 1px, transparent 1px), linear-gradient(to bottom, rgba(0,0,0,0.05) 1px, transparent 1px)`; // Color palette based on theme const colors = isDark ? { - bgPrimary: '#1e1e1e', - bgSecondary: '#252526', - bgTertiary: '#2d2d2d', - borderColor: '#3c3c3c', - textPrimary: '#f3f3f3', - textSecondary: '#808080', - accentBlue: '#007acc', - accentGreen: '#4ec9b0', - currentBg: 'rgba(64, 164, 223, 0.15)', - currentBorder: '#40a4df', - currentRgb: '64, 164, 223', - incomingBg: 'rgba(78, 201, 176, 0.15)', - incomingBorder: '#4ec9b0', - incomingRgb: '78, 201, 176', - baseBg: 'rgba(128, 128, 128, 0.15)', - baseBorder: '#808080', - diffAdd: 'rgba(78, 201, 176, 0.3)', - diffRemove: 'rgba(244, 135, 113, 0.3)', - diffChange: 'rgba(255, 213, 79, 0.3)', - cellSurface: 'rgba(255, 255, 255, 0.08)', - cellSurfaceSoft: 'rgba(255, 255, 255, 0.05)', - cellPlaceholderBg: 'rgba(255, 255, 255, 0.04)', - outputBg: 'rgba(255, 255, 255, 0.06)', - bodyBackground: '#1e1e1e', - bodyBackgroundImage: 'none', + bgPrimary: '#25201b', + bgSecondary: '#2c2620', + bgTertiary: '#373027', + borderColor: 'rgba(255, 255, 255, 0.12)', + textPrimary: '#EFE7DB', + textSecondary: '#B5A998', + accentBlue: '#7FB9C7', + accentGreen: '#9FA8DD', + currentBg: 'rgba(127, 185, 199, 0.24)', + currentBorder: '#7FB9C7', + currentRgb: '127, 185, 199', + incomingBg: 'rgba(159, 168, 221, 0.24)', + incomingBorder: '#9FA8DD', + incomingRgb: '159, 168, 221', + baseBg: 'rgba(181, 169, 152, 0.18)', + baseBorder: '#B5A998', + diffAdd: 'rgba(159, 168, 221, 0.28)', + diffRemove: 'rgba(220, 130, 115, 0.3)', + diffChange: 'rgba(224, 180, 82, 0.28)', + cellSurface: 'rgba(55, 48, 39, 0.82)', + cellSurfaceSoft: 'rgba(55, 48, 39, 0.64)', + cellPlaceholderBg: 'rgba(55, 48, 39, 0.54)', + outputBg: 'rgba(55, 48, 39, 0.72)', + bodyBackground: '#1D1915', + bodyBackgroundImage: DARK_GRID_GRADIENT, + logoLeft: '#7FB9C7', + logoRight: '#9FA8DD', + logoBlendMode: 'normal', } : { // LIGHT theme - inspired by MergeNB logo bgPrimary: '#f1ece3', @@ -50,11 +57,11 @@ export function getStyles(theme: 'dark' | 'light' = 'light'): string { currentBg: 'rgba(164, 212, 222, 0.25)', currentBorder: '#A4D4DE', currentRgb: '164, 212, 222', - incomingBg: 'rgba(195, 201, 242, 0.25)', - incomingBorder: '#C3C9F2', - incomingRgb: '195, 201, 242', - baseBg: 'rgba(128, 128, 128, 0.12)', - baseBorder: '#999999', + incomingBg: 'rgba(159, 168, 221, 0.30)', + incomingBorder: '#9FA8DD', + incomingRgb: '159, 168, 221', + baseBg: 'rgba(128, 128, 128, 0.18)', + baseBorder: '#8b7f70', diffAdd: 'rgba(195, 201, 242, 0.4)', diffRemove: 'rgba(244, 135, 113, 0.35)', diffChange: 'rgba(255, 193, 7, 0.35)', @@ -64,6 +71,9 @@ export function getStyles(theme: 'dark' | 'light' = 'light'): string { outputBg: 'rgba(226, 216, 202, 0.66)', bodyBackground: '#EAE2D5', bodyBackgroundImage: LIGHT_GRID_GRADIENT, + logoLeft: '#A4D4DE', + logoRight: '#C3C9F2', + logoBlendMode: 'multiply', }; const hasBackgroundImage = colors.bodyBackgroundImage !== 'none'; @@ -95,6 +105,9 @@ export function getStyles(theme: 'dark' | 'light' = 'light'): string { --cell-surface-soft: ${colors.cellSurfaceSoft}; --cell-placeholder-bg: ${colors.cellPlaceholderBg}; --output-bg: ${colors.outputBg}; + --logo-left: ${colors.logoLeft}; + --logo-right: ${colors.logoRight}; + --logo-blend-mode: ${colors.logoBlendMode}; } * { @@ -141,7 +154,7 @@ body { .logo-icon { position: relative; width: 60px; - height: 50px; + height: 40px; display: flex; justify-content: center; align-items: center; @@ -149,24 +162,24 @@ body { .logo-card { position: absolute; - width: 34px; - height: 48px; + width: 30px; + height: 36px; border-radius: 6px; - mix-blend-mode: multiply; + mix-blend-mode: var(--logo-blend-mode); opacity: 0.9; transform-origin: 50% 90%; top: 0; } .logo-card-left { - background-color: #A4D4DE; - left: 8px; + background-color: var(--logo-left); + left: 6px; transform: rotate(-25deg); } .logo-card-right { - background-color: #C3C9F2; - right: 8px; + background-color: var(--logo-right); + right: 6px; transform: rotate(25deg); } @@ -370,9 +383,9 @@ body { border-radius: 4px; } -.column-label.base { background: var(--base-bg); color: var(--base-border); } -.column-label.current { background: var(--current-bg); color: var(--current-border); } -.column-label.incoming { background: var(--incoming-bg); color: var(--incoming-border); } +.column-label.base { background: var(--base-bg); color: var(--text-primary); } +.column-label.current { background: var(--current-bg); color: var(--text-primary); } +.column-label.incoming { background: var(--incoming-bg); color: var(--text-primary); } /* Merge rows */ .merge-row { @@ -781,7 +794,7 @@ body { width: 100%; min-height: 200px; padding: 12px; - background: var(--bg-primary); + background: var(--cell-surface); color: var(--text-primary); border: 1px solid var(--border-color); border-radius: 4px; @@ -794,7 +807,7 @@ body { .resolved-cell { margin-top: 12px; padding: 12px; - background: rgba(78, 201, 176, 0.1); + background: var(--cell-surface); border: 2px solid var(--accent-green); border-radius: 6px; } @@ -838,7 +851,7 @@ body { width: 100%; min-height: 120px; padding: 12px; - background: var(--bg-primary); + background: var(--cell-surface); color: var(--text-primary); border: 1px solid rgba(78, 201, 176, 0.4); border-radius: 4px; @@ -1080,7 +1093,7 @@ body { font-style: italic; border: 2px dashed var(--border-color); border-radius: 4px; - background: rgba(128, 128, 128, 0.05); + background: var(--cell-placeholder-bg); } .cell-placeholder.drop-target { @@ -1164,4 +1177,6 @@ export function injectStyles(theme: 'dark' | 'light' = 'light'): void { } // Keep backward compatibility -export const styles = getStyles('light'); +// Do not generate a default light stylesheet at module import time — +// this prevents a light-theme flash on first render when the app wants dark. +export const styles = ''; diff --git a/src/web/webServer.ts b/src/web/webServer.ts index b22dfa4..059f798 100644 --- a/src/web/webServer.ts +++ b/src/web/webServer.ts @@ -47,6 +47,7 @@ export interface PendingConnection { /** Session data stored for each active conflict resolution */ export interface SessionData { htmlContent: string; + theme: 'dark' | 'light'; conflictData?: unknown; onMessage: (message: unknown) => void; } @@ -224,11 +225,13 @@ export class ConflictResolverWebServer { public async openSession( sessionId: string, htmlContent: string, - onMessage: (message: unknown) => void + onMessage: (message: unknown) => void, + theme: 'dark' | 'light' = 'light' ): Promise { // Store session data this.sessions.set(sessionId, { htmlContent, + theme, onMessage }); @@ -329,7 +332,7 @@ export class ConflictResolverWebServer { if (session) { res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); - res.end(this.getHtmlShell(sessionId || 'default')); + res.end(this.getHtmlShell(sessionId || 'default', session.theme)); } else { res.writeHead(404, { 'Content-Type': 'text/html; charset=utf-8' }); res.end(` @@ -431,7 +434,13 @@ export class ConflictResolverWebServer { /** * Generate minimal HTML shell that loads the React app. */ - private getHtmlShell(sessionId: string): string { + private getHtmlShell(sessionId: string, theme: 'dark' | 'light' = 'light'): string { + const isDark = theme === 'dark'; + const loadingBg = isDark ? '#1D1915' : '#EAE2D5'; + const loadingText = isDark ? '#EFE7DB' : '#1A202C'; + const spinnerBorder = isDark ? 'rgba(255, 255, 255, 0.12)' : 'rgba(0, 0, 0, 0.1)'; + const spinnerAccent = isDark ? '#7FB9C7' : '#569cd6'; + return ` @@ -440,7 +449,7 @@ export class ConflictResolverWebServer { MergeNB - Conflict Resolver