From 3d18d295bd3edfc590b6861ede8ff30b359c9f42 Mon Sep 17 00:00:00 2001 From: Puskar-Roy Date: Thu, 9 Apr 2026 09:44:50 +0530 Subject: [PATCH 1/4] feat : split screen --- src/renderer/css/components/split-view.css | 223 ++++++++++++++ src/renderer/index.html | 34 +++ src/renderer/js/app.js | 320 +++++++++++++++++---- 3 files changed, 521 insertions(+), 56 deletions(-) create mode 100644 src/renderer/css/components/split-view.css diff --git a/src/renderer/css/components/split-view.css b/src/renderer/css/components/split-view.css new file mode 100644 index 0000000..7be13ec --- /dev/null +++ b/src/renderer/css/components/split-view.css @@ -0,0 +1,223 @@ +.split-thread { + display: none; + flex-direction: row; + height: 100%; + width: 100%; + overflow: hidden; + gap: 0; +} + +.split-thread.active { + display: flex; +} + +.split-column { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; + height: 100%; + background: rgba(255, 255, 255, 0.02); +} + +.split-column.left { + border-right: none; +} + +.column-header { + padding: 12px 16px; + border-bottom: 1px solid var(--border-color); + background: rgba(0, 0, 0, 0.2); + display: flex; + align-items: center; + justify-content: center; + z-index: 10; +} + +.split-messages { + flex: 1; + overflow-y: auto; + padding: 20px; + display: flex; + flex-direction: column; + gap: 16px; +} + +.split-divider { + width: 1px; + background: var(--border-color); + position: relative; + display: flex; + align-items: center; + justify-content: center; + z-index: 5; +} + +.v-pill { + position: absolute; + top: 10px; + background: var(--bg-color); + border: 1px solid var(--border-color); + color: var(--text-secondary); + font-weight: 800; + font-size: 10px; + width: 24px; + height: 24px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); + transform: translateY(12px); +} + +/* Response Label Style from Mockup */ +.model-response-label { + font-size: 11px; + font-weight: 700; + color: var(--accent-color); + text-transform: uppercase; + letter-spacing: 0.1em; + margin-bottom: 8px; + opacity: 0.8; +} + +/* Adjustments for chat bubbles in split view */ +.split-messages .chat-message-ai, +.split-messages .chat-message-user { + max-width: 100%; + margin-left: 0; + margin-right: 0; + font-size: 13px; + line-height: 1.5; + padding: 12px 16px; + border-radius: 12px; +} + +.split-messages .chat-message-ai { + background: rgba(255, 255, 255, 0.04); +} + +/* Fix for Thinking box (loading state) */ +.split-messages .chat-message-ai:has(.loading) { + background: transparent; + padding: 0 12px; + margin-bottom: 12px; +} + +.split-messages .loading { + margin-top: 0; + background: rgba(255, 255, 255, 0.06); + padding: 10px 16px; + border-radius: 20px; + display: inline-flex; + align-items: center; + border: 1px solid rgba(255, 255, 255, 0.08); + box-shadow: 0 4px 15px rgba(0,0,0,0.2); +} + +.split-messages .loading span { + font-size: 13px; + letter-spacing: 0.02em; +} + +/* Scrollbar styling for split view - Sleeker */ +.split-messages::-webkit-scrollbar { + width: 3px; +} + +.split-messages::-webkit-scrollbar-track { + background: transparent; +} + +.split-messages::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.12); + border-radius: 10px; +} + +.split-messages::-webkit-scrollbar-thumb:hover { + background: var(--accent-color); +} + +/* Dropdown Overrides for Split Headers */ +.column-header .custom-dropdown { + max-width: 280px; +} + +.column-header .dropdown-trigger { + background: rgba(255, 255, 255, 0.04); + border: 1px solid rgba(255, 255, 255, 0.06); + height: 36px; + padding: 0 14px; + border-radius: 10px; +} + +.column-header .dropdown-trigger:hover { + background: rgba(255, 255, 255, 0.08); + border-color: rgba(255, 255, 255, 0.1); +} + +.column-header .dropdown-value { + font-weight: 600; + font-size: 12px; + color: var(--text-primary); +} + +/* Speed and Performance Badges */ +.speed-badge { + font-size: 10px; + font-weight: 600; + color: var(--text-secondary); + background: rgba(255, 255, 255, 0.05); + padding: 2px 6px; + border-radius: 4px; + margin-left: 8px; + vertical-align: middle; +} + +.faster-badge { + color: #ff9d00; + background: rgba(255, 157, 0, 0.1); + border: 1px solid rgba(255, 157, 0, 0.2); + font-size: 9px; + padding: 1px 5px; + text-transform: uppercase; + animation: pulse 2s infinite; +} + +@keyframes pulse { + 0% { opacity: 0.8; } + 50% { opacity: 1; transform: scale(1.05); } + 100% { opacity: 0.8; } +} + +.split-title-header { + font-family: 'Outfit', sans-serif; + font-size: 14px; + font-weight: 700; + color: var(--text-primary); + letter-spacing: 0.03em; + text-transform: uppercase; + display: flex; + align-items: center; + gap: 8px; +} + +.split-title-header::before { + content: ''; + display: block; + width: 3px; + height: 14px; + background: var(--accent-color); + border-radius: 10px; +} + +/* Animations */ +.split-thread.active { + animation: fadeIn 0.3s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; transform: translateY(10px); } + to { opacity: 1; transform: translateY(0); } +} diff --git a/src/renderer/index.html b/src/renderer/index.html index ea1f623..10159fd 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -21,6 +21,7 @@ + @@ -51,6 +52,12 @@ + + +

Model Management

@@ -580,8 +591,8 @@

Global Keyboard Controls

- Quit - Application + Toggle Visibility + (Boss Key) Ctrl + Shift + Q
diff --git a/src/renderer/js/app.js b/src/renderer/js/app.js index b744b38..e45b48b 100644 --- a/src/renderer/js/app.js +++ b/src/renderer/js/app.js @@ -124,14 +124,16 @@ async function init() { function applyAppMode(mode) { try { + scrubTooltips(mode); + + // Fortress Mode: Disable mouse resizing in Stealth Mode if (window.electronAPI && window.electronAPI.setAppMode) { window.electronAPI.setAppMode(mode); } + if (windowControls) { windowControls.style.display = mode === 'normal' ? 'flex' : 'none'; } - - scrubTooltips(mode); // Update dropdown if initialized if (settingsAppMode) settingsAppMode.setValue(mode); @@ -230,22 +232,65 @@ function handleProviderChange(provider) { } } +let tooltipObserver = null; + function scrubTooltips(mode) { try { - const elements = document.querySelectorAll('[title], [data-stealth-title]'); - elements.forEach(el => { - if (mode === 'stealth' || (userConfig && userConfig.appMode === 'stealth')) { - if (el.hasAttribute('title')) { - el.setAttribute('data-stealth-title', el.getAttribute('title')); - el.removeAttribute('title'); - } - } else { - if (el.hasAttribute('data-stealth-title')) { - el.setAttribute('title', el.getAttribute('data-stealth-title')); - el.removeAttribute('data-stealth-title'); + const isStealth = mode === 'stealth' || (userConfig && userConfig.appMode === 'stealth'); + + const performScrub = (root = document) => { + const elements = root.querySelectorAll ? root.querySelectorAll('[title], [data-tooltip]') : []; + elements.forEach(el => { + if (isStealth) { + if (el.hasAttribute('title')) { + el.setAttribute('data-tooltip', el.getAttribute('title')); + el.removeAttribute('title'); + } + } else { + if (el.hasAttribute('data-tooltip')) { + el.setAttribute('title', el.getAttribute('data-tooltip')); + el.removeAttribute('data-tooltip'); + } } - } - }); + }); + }; + + // Initial scrub + performScrub(); + + // Fortress Mode: Aggressive MutationObserver for dynamic elements + if (tooltipObserver) tooltipObserver.disconnect(); + + if (isStealth) { + tooltipObserver = new MutationObserver((mutations) => { + mutations.forEach(mutation => { + mutation.addedNodes.forEach(node => { + if (node.nodeType === 1) { // Element node + performScrub(node); + // Also check the node itself + if (node.hasAttribute('title')) { + node.setAttribute('data-tooltip', node.getAttribute('title')); + node.removeAttribute('title'); + } + } + }); + if (mutation.type === 'attributes' && mutation.attributeName === 'title' && isStealth) { + const el = mutation.target; + if (el.hasAttribute('title')) { + el.setAttribute('data-tooltip', el.getAttribute('title')); + el.removeAttribute('title'); + } + } + }); + }); + + tooltipObserver.observe(document.body, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: ['title'] + }); + } } catch (err) { console.error('[APP] Tooltip scrubbing error:', err); } @@ -682,12 +727,18 @@ function setupEventListeners() { const resetAppBtn = $('reset-app-btn'); if (resetAppBtn) { resetAppBtn.addEventListener('click', () => { - const confirmed = confirm('CRITICAL WARNING: This will permanently delete ALL configurations, API keys, and models. You will be redirected to the onboarding screen. Proceed?'); - - if (confirmed) { - localStorage.clear(); - window.location.reload(); - } + // Removed native confirm() to prevent OS-level leak. + // Action is now immediate but restricted to the Settings -> Profile tab. + localStorage.clear(); + window.location.reload(); + }); + } + + const quitAppBtn = $('quit-app-btn'); + if (quitAppBtn) { + quitAppBtn.addEventListener('click', () => { + // Removed native confirm() to prevent OS-level leak. + window.electronAPI.closeApp(); }); } @@ -1015,6 +1066,18 @@ function setupEventListeners() { } }); } + + // Fortress Mode: Security Listeners + window.addEventListener('contextmenu', (e) => { + // Only block if we aren't in a specific dev-mode context if desired, + // but for stealth we block it everywhere. + e.preventDefault(); + }); + + window.addEventListener('dragstart', (e) => { + // Prevent ghost images during drag to stop OS "shadows" from appearing + e.preventDefault(); + }); } function appendChatMessage(msg, container) { From 873d6826b8a63905c6f785c053460c9e2adbbabe Mon Sep 17 00:00:00 2001 From: Puskar-Roy Date: Sun, 12 Apr 2026 10:10:57 +0530 Subject: [PATCH 4/4] feat: rebrand project to System Utility and implement core Electron main process and UI infrastructure --- package.json | 8 ++--- src/main/index.js | 8 +++++ src/main/shortcuts.js | 5 --- src/main/window-manager.js | 9 +++-- src/renderer/css/base.css | 69 ++++++++++++++++++++++++++++++++++++++ src/renderer/index.html | 3 +- src/renderer/js/app.js | 16 +++++++-- src/renderer/js/ui.js | 34 +++++++++++++++++++ 8 files changed, 136 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index 8bf660c..752e16c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "imposter", + "name": "system-utility", "version": "1.0.0", - "description": "Imposter: Beating a Broken System", + "description": "System Utility: System performance evaluation tool", "author": "Puskar Roy", "repository": { "type": "git", @@ -37,8 +37,8 @@ "prettier": "^3.2.5" }, "build": { - "appId": "com.imposter.app", - "productName": "Imposter", + "appId": "com.system-utility.app", + "productName": "System Utility", "directories": { "output": "dist" }, diff --git a/src/main/index.js b/src/main/index.js index 2151e71..3d28b00 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -2,6 +2,14 @@ const { app, session, BrowserWindow } = require('electron'); const path = require('path'); require('dotenv').config(); +if (app.isPackaged) { + console.log = () => {}; + console.debug = () => {}; + console.info = () => {}; + console.warn = () => {}; + console.error = () => {}; +} + const { createMainWindow, getMainWindow } = require('./window-manager'); const { registerShortcuts, unregisterShortcuts } = require('./shortcuts'); const { registerIpcHandlers } = require('./ipc-handlers'); diff --git a/src/main/shortcuts.js b/src/main/shortcuts.js index 7d266db..10adedb 100644 --- a/src/main/shortcuts.js +++ b/src/main/shortcuts.js @@ -100,11 +100,6 @@ function registerShortcuts() { console.error('[SHORTCUT] Snipping error:', err); } }); - - safeRegister('CommandOrControl+Shift+D', () => { - const win = getMainWindow(); - if (win && !win.isDestroyed()) win.webContents.toggleDevTools(); - }); } function unregisterShortcuts() { diff --git a/src/main/window-manager.js b/src/main/window-manager.js index 85954a2..a717ff6 100644 --- a/src/main/window-manager.js +++ b/src/main/window-manager.js @@ -53,7 +53,8 @@ function createMainWindow(preloadPath) { webPreferences: { nodeIntegration: false, contextIsolation: true, - preload: preloadPath + preload: preloadPath, + devTools: false } }); @@ -97,7 +98,8 @@ function createIslandWindow(preloadPath) { webPreferences: { nodeIntegration: false, contextIsolation: true, - preload: preloadPath + preload: preloadPath, + devTools: false } }); @@ -138,7 +140,8 @@ function createSnipperWindow(preloadPath, screenSource) { enableLargerThanScreen: true, webPreferences: { preload: preloadPath, - contextIsolation: true + contextIsolation: true, + devTools: false } }); diff --git a/src/renderer/css/base.css b/src/renderer/css/base.css index 01504f7..a1c93d5 100644 --- a/src/renderer/css/base.css +++ b/src/renderer/css/base.css @@ -46,3 +46,72 @@ body { ::-webkit-scrollbar-thumb:hover { background: #666; } + +/* Custom HUD Notifications */ +.notification-container { + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 10000; + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; + pointer-events: none; +} + +.notification { + background: rgba(30, 30, 30, 0.95); + color: var(--text-primary); + padding: 12px 20px; + border-radius: 12px; + font-size: 13px; + font-weight: 500; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4); + border: 1px solid var(--border-color); + backdrop-filter: blur(8px); + display: flex; + align-items: center; + gap: 12px; + animation: slideDownIn 0.3s cubic-bezier(0.16, 1, 0.3, 1) forwards; + pointer-events: auto; + max-width: 400px; + width: max-content; +} + +.notification.error { + border-left: 4px solid #ff4d4d; +} + +.notification.success { + border-left: 4px solid var(--accent-color); +} + +.notification-icon { + display: flex; + align-items: center; + justify-content: center; +} + +@keyframes slideDownIn { + from { + opacity: 0; + transform: translateY(-20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.notification.fade-out { + animation: fadeOut 0.3s forwards; +} + +@keyframes fadeOut { + to { + opacity: 0; + transform: translateY(-10px); + } +} diff --git a/src/renderer/index.html b/src/renderer/index.html index e1d1fe7..832eb66 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -5,7 +5,7 @@ - Imposter + System Utility Global Keyboard Controls +
diff --git a/src/renderer/js/app.js b/src/renderer/js/app.js index e45b48b..ea4e2b0 100644 --- a/src/renderer/js/app.js +++ b/src/renderer/js/app.js @@ -9,6 +9,16 @@ window.onerror = (message, source, lineno, colno, error) => { return true; }; +// Global Log Scrubber +const isDev = false; // Set to true for development +if (!isDev) { + console.log = () => {}; + console.debug = () => {}; + console.info = () => {}; + console.warn = () => {}; + console.error = () => {}; +} + window.onunhandledrejection = (event) => { event.preventDefault(); }; @@ -648,7 +658,7 @@ function setupEventListeners() { async function fetchGeminiModelsForForm() { const key = newModelKey ? newModelKey.value.trim() : ''; if (!key || !newGeminiModelSelect) { - alert('Please enter your Gemini API key first.'); + UI.showNotification('Please enter your Gemini API key first.', 'error'); return; } @@ -770,7 +780,7 @@ function setupEventListeners() { newGeminiModelSelect.setOptions([]); } } else if (!modelId && provider === 'gemini') { - alert('Please enter a valid API key and select a model from the list.'); + UI.showNotification('Please enter a valid API key and select a model from the list.', 'error'); } } catch (err) { console.error('[APP] Add model error:', err); @@ -1152,7 +1162,7 @@ async function handleSplitSearch(text) { const rightModelSelection = rightModelSelect.getValue(); if (!leftModelSelection || !rightModelSelection) { - alert('Please select both models for Split-View comparison.'); + UI.showNotification('Please select both models for Split-View comparison.', 'error'); return; } diff --git a/src/renderer/js/ui.js b/src/renderer/js/ui.js index ad04ca1..56ab364 100644 --- a/src/renderer/js/ui.js +++ b/src/renderer/js/ui.js @@ -73,3 +73,37 @@ export function renderReasoningTrace(details) { return ''; } } + +export function showNotification(message, type = 'info', duration = 4000) { + const container = document.getElementById('notification-container'); + if (!container) return; + + const notification = document.createElement('div'); + notification.className = `notification ${type}`; + + let icon = ''; + if (type === 'error') { + icon = ''; + } else if (type === 'success') { + icon = ''; + } else { + icon = ''; + } + + notification.innerHTML = ` +
${icon}
+
${message}
+ `; + + container.appendChild(notification); + + // Auto-remove + setTimeout(() => { + notification.classList.add('fade-out'); + setTimeout(() => { + if (notification.parentNode) { + container.removeChild(notification); + } + }, 300); + }, duration); +}