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
11 changes: 6 additions & 5 deletions src/web/main/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
--accent: #4c8dff;
--accent-soft: #233b61;
--star: #f5c451;
--danger: #f14c4c;
font-family:
Inter,
ui-sans-serif,
Expand Down Expand Up @@ -85,8 +86,10 @@ input:focus-visible,

.repo-tab.active,
.term-tab.active,
.hubs-menu,
.repo-tab.active:hover,
.term-tab.active:hover {
.term-tab.active:hover,
.hubs-menu:hover:not(:disabled) {
background: var(--accent);
border-color: var(--accent);
color: #fff;
Expand Down Expand Up @@ -512,10 +515,8 @@ input:focus-visible,
box-shadow: 0 12px 36px rgb(0 0 0 / 55%);
}

/* Framed thick in the selected tab's blue. */
.hubs-list,
.confirm-dialog.delete-worktree {
border: 3px solid var(--accent);
border: 3px solid var(--danger);
}

/* Undo the user agent's centring and colours of an open dialog. */
Expand Down Expand Up @@ -670,7 +671,7 @@ dialog.add-repo-dialog {
}

.mark-failed {
background: #f14c4c;
background: var(--danger);
}

.mark-exited {
Expand Down
6 changes: 3 additions & 3 deletions test/e2e/contract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export const TID = {
hostLogin: 'host-login',
/** Button inside a host banner: "Restart daemon" (outdated local), "Reinstall & restart" (outdated remote) or "Install" (down remote); opens the confirmation dialog, then sends `restartDaemon` or `reinstallDaemon`. */
hostAction: 'host-action',
/** In-page confirmation of a host action or a repo removal; nothing is sent before it is confirmed; text says every running terminal on the host will be killed when it lists none; for a worktree deletion only, framed 3px in the selected repo tab's background colour. */
/** In-page confirmation of a host action or a repo removal; nothing is sent before it is confirmed; text says every running terminal on the host will be killed when it lists none; for a worktree deletion only, framed 3px in red. */
confirmDialog: 'confirm-dialog',
/** One running terminal the page last saw on the daemon instance the host reports; text holds its worktree label and preset. */
confirmTarget: 'confirm-target',
Expand Down Expand Up @@ -130,9 +130,9 @@ export const TID = {
notice: 'notice',
/** Terminal container, `data-host` and `data-term`; holds the `.xterm` element; hidden ones are not visible. */
terminal: 'terminal',
/** Header button "Hubs", present only while there is a remote host; opens the list of `hubsEntry`. */
/** Header button "Hubs", present only while there is a remote host; opens the list of `hubsEntry`; coloured like the selected repo tab, hovered or not. */
hubsMenu: 'hubs-menu',
/** The open Hubs menu holding the `hubsEntry` items; framed 3px in the selected repo tab's background colour. */
/** The open Hubs menu holding the `hubsEntry` items, with a thin border. */
hubsList: 'hubs-list',
/** One per hub in the open Hubs menu, in order: text = "This machine" or the host name; `aria-current="true"` on the page's own hub. */
hubsEntry: 'hubs-entry',
Expand Down
4 changes: 0 additions & 4 deletions test/e2e/fixture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,6 @@ export const borderOf = (locator: Locator): Promise<string[]> =>
});
});

/** The computed background colour of the selected repo tab. */
export const selectedTabColour = (page: Page): Promise<string> =>
page.locator(`${byTestId(TID.repoTab)}[aria-selected="true"]`).evaluate((el) => getComputedStyle(el).backgroundColor);

/** Confirms the open confirmation dialog. */
export const confirmAction = async (page: Page): Promise<void> => {
const dialog = page.locator(byTestId(TID.confirmDialog));
Expand Down
25 changes: 20 additions & 5 deletions test/e2e/hubs.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Page } from '@playwright/test';
import type { Locator, Page } from '@playwright/test';
import { join } from 'node:path';
import { makeRepo } from '../support/daemon-host.js';
import { freePort, type HubHost } from '../support/hub-host.js';
Expand All @@ -12,7 +12,6 @@ import {
nodeRemote,
openUi,
runUi,
selectedTabColour,
test,
writeHubConfig,
type HostConfig,
Expand Down Expand Up @@ -82,12 +81,28 @@ test.describe('Hubs menu', () => {
await expect(page.locator(byTestId(TID.hubsMenu))).toBeFocused();
});

test("is framed 3px in the selected tab's colour", async ({ hub, page, ssh }) => {
test('the open menu has a thin border', async ({ hub, page, ssh }) => {
ssh.failHost('box', RESOLVE_ERROR);
await home(hub, page, [{ name: 'box', ssh: 'box' }]);
await page.locator(byTestId(TID.hubsMenu)).click();
const blue = await selectedTabColour(page);
expect(await borderOf(page.locator(byTestId(TID.hubsList)))).toEqual(Array(4).fill(`3px ${blue}`));
expect((await borderOf(page.locator(byTestId(TID.hubsList)))).every((side) => side.startsWith('1px '))).toBe(true);
});

test('the Hubs button is coloured like the selected tab, hovered or not', async ({ hub, page, ssh }) => {
ssh.failHost('box', RESOLVE_ERROR);
await home(hub, page, [{ name: 'box', ssh: 'box' }]);
const button = page.locator(byTestId(TID.hubsMenu));
const selected = page.locator(`${byTestId(TID.repoTab)}[aria-selected="true"]`);
const colours = (locator: Locator): Promise<string[]> =>
locator.evaluate((el) => {
const style = getComputedStyle(el);
return [style.backgroundColor, style.borderTopColor, style.color];
});
await page.mouse.move(0, 0);
const tab = await colours(selected);
await expect.poll(() => colours(button)).toEqual(tab);
await button.hover();
await expect.poll(() => colours(button)).toEqual(tab);
});

test('is absent without remote hosts', async ({ hub, page }) => {
Expand Down
10 changes: 5 additions & 5 deletions test/e2e/sidebar.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
openPage,
openUi,
ready,
selectedTabColour,
selectRepo,
selectWorktree,
test,
Expand All @@ -26,6 +25,8 @@ import {
watcher,
} from './fixture.js';

/** `--danger`, as computed. */
const DANGER = 'rgb(241, 76, 76)';
const label = (path: string): string => `${worktreeEntry(path)} ${byTestId(TID.worktreeLabel)}`;
const checkbox = (path: string): string => `${worktreeEntry(path)} input[type="checkbox"]`;
const boxOf = async (locator: Locator): Promise<{ x: number; y: number; width: number; height: number }> => {
Expand Down Expand Up @@ -471,7 +472,7 @@ test.describe('worktree status and deletion', () => {
expect(git(repo, 'rev-parse', 'feat')).toMatch(/^[0-9a-f]{40}$/);
});

test("the deletion dialog is framed 3px in the selected tab's colour; a repo removal's is not", async ({ hub, page }) => {
test("the deletion dialog is framed 3px in red; a repo removal's is not", async ({ hub, page }) => {
const { repo, worktrees } = makeRepoWith(hub, 'app', ['feat']);
const feat = worktrees[0] ?? '';
withOrigin(hub, repo);
Expand All @@ -482,13 +483,12 @@ test.describe('worktree status and deletion', () => {
await page.locator(worktreeEntry(feat)).click({ button: 'right' });
await page.locator(byTestId(TID.deleteWorktree)).click();
await expect(dialog).toBeVisible();
const blue = await selectedTabColour(page);
expect(await borderOf(dialog)).toEqual(Array(4).fill(`3px ${blue}`));
expect(await borderOf(dialog)).toEqual(Array(4).fill(`3px ${DANGER}`));
await dialog.locator(byTestId(TID.confirmCancel)).click();

await page.locator(repoTab(repo)).locator(byTestId(TID.removeRepo)).click();
await expect(dialog).toBeVisible();
expect((await borderOf(dialog)).every((side) => side.startsWith('1px ') && !side.endsWith(blue))).toBe(true);
expect((await borderOf(dialog)).every((side) => side.startsWith('1px ') && !side.endsWith(DANGER))).toBe(true);
});

test('deleting a detached worktree with commits says they become unreachable and names its head', async ({ hub, page }) => {
Expand Down
Loading