Skip to content

Commit 8a1b81f

Browse files
author
Factory Bot
committed
feat(gui): pixel-perfect Figma UI redesign - Inline navbar menu with hover dropdowns - New Cortex logo branding - Tab styling with seamless editor fusion - Background color #131217 - Remove footer and empty state cards - Simplified Session.tsx
1 parent cb7618c commit 8a1b81f

23 files changed

Lines changed: 993 additions & 1484 deletions

cortex-gui/mcp-server/src/index.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -380,26 +380,17 @@ function registerTools() {
380380

381381
// Main entry point
382382
async function main() {
383-
try {
384-
// Connect to Cortex Desktop socket server
385-
console.error("[MCP Server] Connecting to Cortex Desktop...");
386-
await socketClient.connect();
387-
console.error("[MCP Server] Connected to Cortex Desktop");
388-
389-
// Register all tools
390-
registerTools();
391-
392-
// Start the MCP server with stdio transport
393-
const transport = new StdioServerTransport();
394-
await server.connect(transport);
395-
console.error("[MCP Server] Cortex Desktop MCP Server running on stdio");
396-
} catch (error) {
397-
console.error("[MCP Server] Fatal error:", error);
398-
process.exit(1);
399-
}
383+
// Register all tools FIRST (before connecting transport)
384+
registerTools();
385+
386+
// Create and connect stdio transport
387+
const transport = new StdioServerTransport();
388+
await server.connect(transport);
389+
390+
// Connect to Cortex Desktop socket in background (don't block MCP initialization)
391+
socketClient.connect().catch(() => {
392+
// Silent fail - Cortex Desktop may not be running
393+
});
400394
}
401395

402-
main().catch((error) => {
403-
console.error("[MCP Server] Fatal error:", error);
404-
process.exit(1);
405-
});
396+
main();

cortex-gui/src-tauri/src/window.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,7 @@ pub async fn create_new_window(app: AppHandle, path: Option<String>) -> Result<(
308308
url.push_str(&format!("&project={}", url_encode(&normalized)));
309309
}
310310

311-
let window = WebviewWindowBuilder::new(&app, label, WebviewUrl::App(url.into()))
311+
let window = WebviewWindowBuilder::new(&app, label.clone(), WebviewUrl::App(url.into()))
312312
.title("Cortex")
313313
.inner_size(1200.0, 800.0)
314314
.decorations(false) // Custom title bar is used
@@ -320,6 +320,22 @@ pub async fn create_new_window(app: AppHandle, path: Option<String>) -> Result<(
320320
// Apply vibrancy effect for glassmorphism
321321
apply_window_vibrancy(&window);
322322

323+
// Fallback: show window after 3 seconds if frontend hasn't signaled ready
324+
// This prevents invisible windows if frontend fails to load
325+
let window_clone = window.clone();
326+
let label_clone = label.clone();
327+
tauri::async_runtime::spawn(async move {
328+
tokio::time::sleep(std::time::Duration::from_secs(3)).await;
329+
// Only show if window is still not visible
330+
if let Ok(visible) = window_clone.is_visible() {
331+
if !visible {
332+
info!("Fallback: showing window {} after timeout", label_clone);
333+
let _ = window_clone.show();
334+
let _ = window_clone.set_focus();
335+
}
336+
}
337+
});
338+
323339
Ok(())
324340
}
325341

cortex-gui/src/components/editor/CodeEditor.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5932,7 +5932,8 @@ const commandMap: Record<string, string> = {
59325932
<div
59335933
class="flex-1 flex flex-col overflow-hidden relative transition-all duration-300"
59345934
style={{
5935-
background: "var(--jb-canvas)",
5935+
// Figma design: Editor background must match active tab for seamless fusion
5936+
background: "var(--figma-bg-secondary, #1A1A1A)",
59365937
"box-shadow": agentActive() ? "inset 0 0 0 2px #f97316" : "none", // Orange border when agent is active
59375938
}}
59385939
onDragEnter={handleDragEnter}

cortex-gui/src/components/editor/EditorGridPanel.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
import { EditorGrid, type EditorGridState, type DropPosition } from "./EditorGrid";
2222
import { useEditor } from "@/context/EditorContext";
2323
import { CodeEditor } from "./CodeEditor";
24-
import { Breadcrumbs } from "./Breadcrumbs";
2524
import { ImageViewer, isImageFile, SVGPreview, isSVGFile } from "../viewers";
2625
import { Card, Text } from "@/components/ui";
2726
import { Icon } from "../ui/Icon";
@@ -188,9 +187,6 @@ function EditorCellRenderer(props: EditorCellRendererProps) {
188187
"outline-offset": "-2px",
189188
}}
190189
>
191-
{/* Breadcrumbs */}
192-
<Breadcrumbs file={fileAccessor()} groupId={props.cellId} />
193-
194190
{/* Content based on file type */}
195191
<Show
196192
when={isSvg()}

cortex-gui/src/components/editor/MultiBuffer.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { Show, createSignal, createMemo, createEffect, onMount, onCleanup, JSX,
22
import { useEditor, SplitDirection, OpenFile, EditorGroup } from "@/context/EditorContext";
33
import { CodeEditor } from "./CodeEditor";
44
import { TabBar } from "./TabBar";
5-
import { Breadcrumbs } from "./Breadcrumbs";
65
import { ImageViewer, isImageFile, SVGPreview, isSVGFile } from "../viewers";
76
import { Icon } from "../ui/Icon";
87
import { Card, Text } from "@/components/ui";
@@ -101,7 +100,8 @@ function EditorGroupPanel(props: EditorGroupPanelProps) {
101100
style={{
102101
outline: props.isActive ? "2px solid var(--accent)" : "none",
103102
"outline-offset": "-2px",
104-
background: "var(--background-base)",
103+
// Figma design: Container background for tab bar area
104+
background: "var(--figma-bg-primary, #131217)",
105105
}}
106106
onClick={props.onActivate}
107107
onDragOver={handleDragOver}
@@ -241,8 +241,6 @@ function FileViewer(props: FileViewerProps) {
241241
when={isNonSvgImage()}
242242
fallback={
243243
<div class="flex-1 flex flex-col min-h-0 overflow-hidden">
244-
{/* Breadcrumbs navigation - positioned below tabs, above editor */}
245-
<Breadcrumbs file={props.file} groupId={props.groupId} />
246244
<CodeEditor file={props.file} groupId={props.groupId} />
247245
</div>
248246
}

cortex-gui/src/components/editor/TabBar.tsx

Lines changed: 70 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ interface TabProps {
181181
dropPosition?: "left" | "right" | null;
182182
isFirstTab?: boolean;
183183
isLastTab?: boolean;
184+
isBeforeActive?: boolean;
185+
isAfterActive?: boolean;
184186
/** Tab decorations from diagnostics/git */
185187
decoration?: TabDecoration;
186188
}
@@ -251,48 +253,66 @@ function Tab(props: TabProps) {
251253
if (props.dropPosition === "right") classes.push("drop-target-right");
252254
if (props.isFirstTab) classes.push("first-tab");
253255
if (props.isLastTab) classes.push("last-tab");
256+
if (props.isBeforeActive) classes.push("before-active");
257+
if (props.isAfterActive) classes.push("after-active");
254258

255259
// Icon class for padding adjustment
256260
classes.push("has-icon", "tab-actions-right");
257261

258262
return classes.join(" ");
259263
});
260264

261-
// Tab dimensions based on design specs - Trae Dark Minimal Floating Card Style
265+
// Tab dimensions based on Figma design specs
266+
// Active tab: #1A1A1A background, merged with editor (no bottom border)
267+
// Inactive tabs: transparent background, muted text color
262268
const getTabStyle = () => {
263269
const baseStyle: Record<string, string> = {
264270
"box-sizing": "border-box",
265-
padding: "0 14px",
271+
padding: "0 16px",
266272
"outline-offset": "-2px",
267-
border: "none", // NO borders in Trae style
273+
border: "none",
274+
display: "flex",
275+
"align-items": "center",
276+
gap: "8px",
268277
};
269278

270279
// Apply decoration color override
271280
const dec = decoration();
272281
const decorationColor = dec.color;
273282

274-
// Trae Dark Minimal: Active tab merges with editor
275-
// JetBrains compact tabs: 28px height
283+
// Figma design: Active tab merges seamlessly with editor (#1A1A1A)
276284
if (props.isActive) {
277-
baseStyle.height = "28px";
278-
// Rounded top corners (6px), flat bottom to merge with editor
279-
baseStyle["border-radius"] = "6px 6px 0 0";
280-
baseStyle.background = tokens.colors.surface.canvas;
281-
baseStyle.color = decorationColor ?? tokens.colors.text.primary;
282-
// Subtle shadow for floating card effect
283-
baseStyle["box-shadow"] = "0 -1px 3px var(--jb-shadow-color, rgba(0, 0, 0, 0.15))";
285+
baseStyle.height = "100%";
286+
baseStyle["border-radius"] = "0";
287+
// Same background as editor panel - seamless merge
288+
baseStyle.background = "var(--figma-bg-secondary, #1A1A1A)";
289+
baseStyle.color = decorationColor ?? "var(--figma-text-primary, #FFFFFF)";
290+
baseStyle["box-shadow"] = "none";
284291
baseStyle["z-index"] = "10";
292+
// Active tab: NO border, merges with editor
293+
baseStyle.border = "none";
285294
} else {
286-
// Inactive tabs are same height with muted text
287-
baseStyle.height = "28px";
288-
baseStyle["border-radius"] = "4px 4px 0 0";
289-
baseStyle.background = isHovered() ? tokens.colors.interactive.hover : "transparent";
290-
// Apply decoration color or default colors
295+
// Inactive tabs: #131217 background, muted text
296+
baseStyle.height = "100%";
297+
baseStyle["border-radius"] = "0";
298+
baseStyle.background = isHovered()
299+
? "rgba(255, 255, 255, 0.06)"
300+
: "#131217";
301+
// Figma: subtle border - only right border to avoid double borders
302+
baseStyle["border-top"] = "1px solid rgba(255, 255, 255, 0.15)";
303+
baseStyle["border-bottom"] = "1px solid rgba(255, 255, 255, 0.15)";
304+
baseStyle["border-right"] = "1px solid rgba(255, 255, 255, 0.15)";
305+
baseStyle["border-left"] = "none";
291306
if (decorationColor) {
292307
baseStyle.color = decorationColor;
293308
} else {
294-
baseStyle.color = isHovered() ? tokens.colors.text.primary : tokens.colors.text.muted;
309+
// Figma design: inactive tabs have more muted text (#666 vs #808080)
310+
baseStyle.color = isHovered()
311+
? "var(--figma-text-primary, #FFFFFF)"
312+
: "rgba(255, 255, 255, 0.8)";
295313
}
314+
// Inactive tabs slightly dimmed per Figma design
315+
baseStyle.opacity = isHovered() ? "1" : "0.85";
296316
}
297317

298318
// Sticky compact = 38px × 38px
@@ -311,7 +331,7 @@ function Tab(props: TabProps) {
311331
// Fit mode = minimum space needed (80px min, 200px max)
312332
else if (sizingMode() === "fit") {
313333
baseStyle["min-width"] = "80px";
314-
baseStyle["max-width"] = "200px";
334+
baseStyle["max-width"] = "fit-content";
315335
baseStyle["flex-shrink"] = "0";
316336
baseStyle["flex-grow"] = "0";
317337
baseStyle.width = "auto";
@@ -432,12 +452,10 @@ function Tab(props: TabProps) {
432452

433453
{/* Tab content - 8px gap for proper icon-to-text spacing */}
434454
<div
435-
class="tab-label flex items-center min-w-0 w-full"
455+
class="tab-label flex items-center w-full"
436456
style={{
437457
"line-height": "35px",
438-
flex: "1",
439458
"white-space": "nowrap",
440-
overflow: "hidden",
441459
gap: "8px", // Design spec: 8px spacing from text
442460
}}
443461
>
@@ -456,7 +474,7 @@ function Tab(props: TabProps) {
456474

457475
{/* File icon - 16px with 8px spacing (hidden in compact pinned mode) */}
458476
<Show when={!(props.isPinned && stickyMode() === "compact")}>
459-
<FileIcon name={props.file.name} size={16} />
477+
<FileIcon name={props.file.name.split(/[/\\\\]/).pop() || props.file.name} size={16} />
460478
</Show>
461479

462480
{/* Pin icon indicator for pinned tabs (shown after file icon in non-compact mode) */}
@@ -477,20 +495,22 @@ function Tab(props: TabProps) {
477495
{/* Preview tabs have italic title, deleted files have strikethrough */}
478496
<Show when={!(props.isPinned && stickyMode() === "compact")}>
479497
<span
480-
class="truncate flex-1"
498+
481499
style={{
482500
"font-size": "var(--jb-font-size-sm)",
483501
"font-style": (props.isPreview || decoration().italic) ? "italic" : "normal",
484502
"font-weight": props.isActive ? "500" : "400",
485503
"text-decoration": decoration().strikethrough ? "line-through" : "none",
486-
// Use decoration color if available, otherwise default colors
504+
// Full filename display with ellipsis for very long names
487505
color: decoration().color ?? (props.isActive
488506
? tokens.colors.text.primary
489507
: tokens.colors.text.muted),
490508
transition: "color 150ms ease-out, font-style 150ms ease-out, text-decoration 150ms ease-out",
509+
491510
}}
511+
title={props.file.name.split(/[/\\\\]/).pop() || props.file.name}
492512
>
493-
{props.file.name}
513+
{props.file.name.split(/[/\\\\]/).pop() || props.file.name}
494514
</span>
495515
</Show>
496516

@@ -955,16 +975,16 @@ export function TabBar(props: TabBarProps) {
955975

956976
return (
957977
<div
958-
class={`tabs-and-actions-container relative flex items-end shrink-0 ${wrapTabs() ? "wrapping" : ""}`}
978+
class={`tabs-and-actions-container relative flex items-stretch shrink-0 ${wrapTabs() ? "wrapping" : ""}`}
959979
style={{
960-
// JetBrains compact: 28px tab bar height
961-
height: wrapTabs() ? "auto" : "28px",
962-
"min-height": "28px",
963-
// Inherit from parent card for consistent surface color
964-
background: "inherit",
980+
// Figma design: 47px tab bar height
981+
height: wrapTabs() ? "auto" : "36px",
982+
"min-height": "36px",
983+
// Figma: Dark background for tab bar, active tab merges with editor
984+
background: "var(--figma-bg-primary, #131217)",
965985
border: "none",
966986
// Minimal padding
967-
padding: "0 2px",
987+
padding: "0",
968988
}}
969989
>
970990
{/* Scroll left button - hidden when tab wrapping is enabled */}
@@ -973,8 +993,7 @@ export function TabBar(props: TabBarProps) {
973993
onClick={scrollLeft}
974994
class="w-6 flex items-center justify-center transition-colors z-10 rounded"
975995
style={{
976-
height: "28px",
977-
color: tokens.colors.text.muted,
996+
height: "100%", color: tokens.colors.text.muted,
978997
border: "none",
979998
"margin-right": "2px",
980999
}}
@@ -989,7 +1008,7 @@ export function TabBar(props: TabBarProps) {
9891008
class={`tabs-container flex-1 flex items-stretch no-scrollbar ${wrapTabs() ? "wrapping" : "overflow-x-auto"}`}
9901009
style={{
9911010
// JetBrains compact: 28px height
992-
height: wrapTabs() ? "auto" : "28px",
1011+
height: wrapTabs() ? "auto" : "36px",
9931012
"scrollbar-width": "none", // Firefox
9941013
...(wrapTabs() ? { "flex-wrap": "wrap" } : {}),
9951014
}}
@@ -1003,9 +1022,18 @@ export function TabBar(props: TabBarProps) {
10031022
const isLast = index() === sortedFiles().length - 1;
10041023
const isPinned = isTabPinned(file.id);
10051024

1025+
// Check if this tab is adjacent to the active tab
1026+
const files = sortedFiles();
1027+
const currentActiveId = activeFileId();
1028+
const activeIdx = files.findIndex(f => f.id === currentActiveId);
1029+
const currentIdx = index();
1030+
const isBeforeActive = currentIdx === activeIdx - 1 && activeIdx > 0;
1031+
const isAfterActive = currentIdx === activeIdx + 1 && activeIdx < files.length - 1;
1032+
10061033
return (
10071034
<div
10081035
ref={tabRef}
1036+
style={{ display: "contents" }}
10091037
onDragOver={(e) => tabRef && handleDragOver(e, file.id, tabRef)}
10101038
onDragLeave={handleDragLeave}
10111039
onDrop={(e) => handleDrop(e, file.id)}
@@ -1041,6 +1069,8 @@ export function TabBar(props: TabBarProps) {
10411069
dropPosition={state.overId === file.id ? state.position : null}
10421070
isFirstTab={isFirst}
10431071
isLastTab={isLast}
1072+
isBeforeActive={isBeforeActive}
1073+
isAfterActive={isAfterActive}
10441074
/>
10451075
</div>
10461076
);
@@ -1061,8 +1091,7 @@ export function TabBar(props: TabBarProps) {
10611091
onClick={scrollRight}
10621092
class="w-6 flex items-center justify-center transition-colors z-10 rounded"
10631093
style={{
1064-
height: "28px",
1065-
color: tokens.colors.text.muted,
1094+
height: "100%", color: tokens.colors.text.muted,
10661095
border: "none",
10671096
"margin-left": "2px",
10681097
}}
@@ -1085,8 +1114,7 @@ export function TabBar(props: TabBarProps) {
10851114
}}
10861115
class="w-7 flex items-center justify-center transition-colors rounded"
10871116
style={{
1088-
height: "28px",
1089-
color: tokens.colors.text.muted,
1117+
height: "100%", color: tokens.colors.text.muted,
10901118
border: "none",
10911119
"margin-left": tokens.spacing.sm,
10921120
}}
@@ -1103,8 +1131,7 @@ export function TabBar(props: TabBarProps) {
11031131
onClick={() => setShowOverflow(!showOverflow())}
11041132
class="w-7 flex items-center justify-center transition-colors rounded"
11051133
style={{
1106-
height: "28px",
1107-
color: tokens.colors.text.muted,
1134+
height: "100%", color: tokens.colors.text.muted,
11081135
border: "none",
11091136
}}
11101137
title="Show all tabs"
@@ -1132,8 +1159,7 @@ export function TabBar(props: TabBarProps) {
11321159
}}
11331160
class="w-7 flex items-center justify-center transition-colors rounded"
11341161
style={{
1135-
height: "28px",
1136-
color: tokens.colors.text.muted,
1162+
height: "100%", color: tokens.colors.text.muted,
11371163
border: "none",
11381164
}}
11391165
title="Close split"

0 commit comments

Comments
 (0)