Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and
- **Edit overrides in VS Code.** **Open in VS Code**, on an override's tab, its right-click menu in the Explorer or the command palette, opens its file there. Save it in VS Code, or any other editor, and the page reloads with your version, just as if you had saved in the app. A tab left open shows the new text; if it has edits you haven't saved, it keeps them and offers the saved file's version instead. **Show in folder** finds the file for any other editor.
- **Share your overrides.** The new menu on the Explorer's **Overrides** section (or the command palette) exports a workspace's overrides and rules as one file, and imports one: send it to a teammate and they see the page with your fix, or copy your changes to another workspace. An import adds only what the workspace doesn't have yet, so your own versions are kept, and tells you what it left out.

### Changed

- **No more white title bar on Linux.** The editor's window no longer has the system's light title bar above its own. The app's dark title bar is now the top of the window: drag it to move the window, and the minimize, maximize and close buttons sit at its right end, in its colours.

## [0.4.0] - 2026-09-26

Console Editor now shows how a site works, not just what it loaded. Pick any element to see the React, Vue, Angular or web component behind it, traced to its source file even in a production build, and record why components rendered and what the page's stores did. A new Network panel overrides API responses and pauses requests, rules block requests and change headers, and actions run code in any frame with one click.
Expand Down
2 changes: 1 addition & 1 deletion docs/DESIGN_SYSTEM.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ All colors are CSS custom properties in `src/renderer/src/app/styles/tokens.css`

### Spacing and density

4 px grid. Rows are 26 px (tree) / 28 px (lists), controls 28 px (`h-7`), title bar 44 px, status bar 26 px, activity rail 48 px wide. The rail holds the views (Explorer, Actions, Inspect, Search), a hairline, the workspaces (28 px tiles in 36 px hit areas; the active one gets a `--fg` bar on the left, gliding with `SPRING_LAYOUT`, the others sit at 65 % opacity), +, and Settings at the bottom.
4 px grid. Rows are 26 px (tree) / 28 px (lists), controls 28 px (`h-7`), title bar 44 px (on Linux also the window's: it is dragged to move it, and the window buttons sit over its right end, past `--titlebar-controls-w` of padding), status bar 26 px, activity rail 48 px wide. The rail holds the views (Explorer, Actions, Inspect, Search), a hairline, the workspaces (28 px tiles in 36 px hit areas; the active one gets a `--fg` bar on the left, gliding with `SPRING_LAYOUT`, the others sit at 65 % opacity), +, and Settings at the bottom.

---

Expand Down
4 changes: 2 additions & 2 deletions docs/SPEC.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions src/main/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,11 @@ export const IMAGE_DATA_URL = /^data:image\/[\w.+-]+[;,]/i;
/** Matches the --canvas token, so nothing flashes before a window's UI paints. */
export const CANVAS_COLOR = '#08080a';

/** Matches the --fg-muted token: the window buttons drawn over the title bar, as quiet as its other icons. */
export const TITLE_BAR_SYMBOL_COLOR = '#9d9ea5';

/** The editor's title bar (--titlebar-h, 44 px) less its bottom border, which runs on under the window buttons. */
export const TITLE_BAR_HEIGHT = 43;

/** Where electron-vite puts the preload script, relative to the main bundle. */
export const PRELOAD_SCRIPT = '../preload/index.js';
15 changes: 12 additions & 3 deletions src/main/launch/createEditorWindow.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
import { BrowserWindow } from 'electron';
import { BrowserWindow, type BrowserWindowConstructorOptions } from 'electron';
import { join } from 'node:path';
import appIcon from '../../../build/icons/512x512.png?asset&asarUnpack';
import { CANVAS_COLOR, PRELOAD_SCRIPT } from '../constants';
import { CANVAS_COLOR, PRELOAD_SCRIPT, TITLE_BAR_HEIGHT, TITLE_BAR_SYMBOL_COLOR } from '../constants';

/** The window's size when it first opens, and the smallest it can be made. */
const WINDOW_SIZE = { width: 1600, height: 1000 };
const MIN_WINDOW_SIZE = { width: 960, height: 600 };

/**
* On Linux the system's title bar (a light bar naming the app, above the editor's own) gives way to the
* editor's title bar, with the window buttons drawn over its right end in its colours.
*/
const LINUX_TITLE_BAR: BrowserWindowConstructorOptions = {
titleBarStyle: 'hidden',
titleBarOverlay: { color: CANVAS_COLOR, symbolColor: TITLE_BAR_SYMBOL_COLOR, height: TITLE_BAR_HEIGHT },
};

/** The editor's window, hidden until its UI is ready to show. */
export function createEditorWindow(): BrowserWindow {
return new BrowserWindow({
Expand All @@ -16,7 +25,7 @@ export function createEditorWindow(): BrowserWindow {
minHeight: MIN_WINDOW_SIZE.height,
title: 'Console Editor',
// Elsewhere the window takes the app's own icon; on Linux it has to be given one.
...(process.platform === 'linux' ? { icon: appIcon } : {}),
...(process.platform === 'linux' ? { icon: appIcon, ...LINUX_TITLE_BAR } : {}),
backgroundColor: CANVAS_COLOR,
// The app menu keeps its shortcuts; on Windows/Linux Alt shows the bar.
autoHideMenuBar: true,
Expand Down
3 changes: 3 additions & 0 deletions src/renderer/src/app/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@

/* Layout */
--titlebar-h: 44px;
/* What the window buttons drawn over the title bar's right end take (Linux, where the system's title bar
gives way to the app's): the width Window Controls Overlay leaves out of the title bar; 0 without one. */
--titlebar-controls-w: calc(100vw - env(titlebar-area-x, 0px) - env(titlebar-area-width, 100vw));
--statusbar-h: 26px;
--rail-w: 48px;
/* Width of the website preview; set by the editor page while it is shown. */
Expand Down
13 changes: 9 additions & 4 deletions src/renderer/src/widgets/title-bar/ui/TitleBar/TitleBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,16 @@ export function TitleBar({ onOpenPalette, sidebarVisible, previewVisible, onTogg
const errors = useConsoleStore(selectErrorCount);

return (
<header className="flex h-[var(--titlebar-h)] shrink-0 items-center gap-3 border-b border-line bg-canvas px-3" data-testid="title-bar">
// The window's title bar where the system's gives way to it (Linux): dragging it moves the window, and its
// right end leaves room for the window buttons. Its controls stay clickable.
<header
className="flex h-[var(--titlebar-h)] shrink-0 items-center gap-3 border-b border-line bg-canvas pl-3 pr-[calc(0.75rem+var(--titlebar-controls-w))] [app-region:drag]"
data-testid="title-bar"
>
<BrandMark />
<div className="flex min-w-0 items-center gap-2 text-[13px]">
<span
className="flex max-w-[260px] items-center gap-1.5 rounded-lg border border-line bg-surface-raised/60 px-2 py-1 text-fg"
className="flex max-w-[260px] items-center gap-1.5 rounded-lg border border-line bg-surface-raised/60 px-2 py-1 text-fg [app-region:no-drag]"
title={url ? `${title}\n${url}` : 'No site open'}
>
<Icon icon={icons.GlobeIcon} size={13} className={url ? 'text-info' : 'text-fg-subtle'} />
Expand All @@ -64,7 +69,7 @@ export function TitleBar({ onOpenPalette, sidebarVisible, previewVisible, onTogg
onClick={onOpenPalette}
data-testid="palette-trigger"
className={cn(
'flex h-7 w-[280px] items-center gap-2 rounded-lg border border-line bg-surface-raised/60 px-2.5 text-[12.5px] text-fg-subtle',
'flex h-7 w-[280px] items-center gap-2 rounded-lg border border-line bg-surface-raised/60 px-2.5 text-[12.5px] text-fg-subtle [app-region:no-drag]',
'transition-[border-color,background-color,color] duration-150 ease-out-expo hover:border-line-strong hover:bg-surface-raised hover:text-fg-muted',
)}
>
Expand All @@ -73,7 +78,7 @@ export function TitleBar({ onOpenPalette, sidebarVisible, previewVisible, onTogg
<Kbd keys={SHORTCUT.palette} />
</button>

<div className="flex items-center gap-0.5">
<div className="flex items-center gap-0.5 [app-region:no-drag]" data-testid="layout-toggles">
<IconButton icon={icons.SidebarLeftIcon} label={sidebarVisible ? 'Hide sidebar' : 'Show sidebar'} shortcut={SHORTCUT.sidebar} aria-pressed={sidebarVisible} onClick={onToggleSidebar} tooltipSide={TOOLTIP_SIDE} />
<IconButton
icon={icons.ConsoleIcon}
Expand Down
22 changes: 22 additions & 0 deletions test/e2e/app.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,28 @@ describe.skipIf(!built)('Console Editor app', () => {
await rm(userData, { recursive: true, force: true, maxRetries: 5 });
});

it.skipIf(process.platform !== 'linux')("takes the system's title bar's place on Linux, with room for the window buttons", async () => {
const bar = await win.evaluate(() => {
// Window Controls Overlay: not in TypeScript's DOM types yet.
const { windowControlsOverlay } = navigator as Navigator & { windowControlsOverlay: { visible: boolean; getTitlebarAreaRect(): DOMRect } };
const area = windowControlsOverlay.getTitlebarAreaRect();
const region = (testId: string) => getComputedStyle(document.querySelector(`[data-testid="${testId}"]`)!).getPropertyValue('app-region');
return {
overlay: windowControlsOverlay.visible,
areaRight: area.x + area.width,
togglesRight: document.querySelector('[data-testid="layout-toggles"]')!.getBoundingClientRect().right,
bar: region('title-bar'),
toggles: region('layout-toggles'),
palette: region('palette-trigger'),
};
});
// Electron draws the window buttons over the bar's right end; the bar's own controls end before them.
expect(bar.overlay).toBe(true);
expect(bar.togglesRight).toBeLessThanOrEqual(bar.areaRight);
// Dragging the bar moves the window; its controls are clicked, not dragged.
expect(bar).toMatchObject({ bar: 'drag', toggles: 'no-drag', palette: 'no-drag' });
});

it('opens a website and lists its scripts, stylesheets and document', async () => {
await goTo(win, site.url);
await fileRow(win, `${site.url}/app.js`).waitFor();
Expand Down
Loading