feat(app): sidebar reveal animation, hover peek overlay, and weaker d… · argszero/opencode@b0bc3d8 · GitHub
Skip to content

Commit b0bc3d8

Browse files
feat(app): sidebar reveal animation, hover peek overlay, and weaker dividers (anomalyco#16374)
Co-authored-by: Adam <2363879+adamdotdevin@users.noreply.github.com>
1 parent a263433 commit b0bc3d8

21 files changed

Lines changed: 482 additions & 367 deletions

packages/app/e2e/actions.ts

Lines changed: 48 additions & 45 deletions

packages/app/e2e/app/titlebar-history.spec.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ test("titlebar back/forward navigates between sessions", async ({ page, slug, sd
1616

1717
const link = page.locator(`[data-session-id="${two.id}"] a`).first()
1818
await expect(link).toBeVisible()
19-
await link.scrollIntoViewIfNeeded()
2019
await link.click()
2120

2221
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
@@ -56,7 +55,6 @@ test("titlebar forward is cleared after branching history from sidebar", async (
5655

5756
const second = page.locator(`[data-session-id="${b.id}"] a`).first()
5857
await expect(second).toBeVisible()
59-
await second.scrollIntoViewIfNeeded()
6058
await second.click()
6159

6260
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${b.id}(?:\\?|#|$)`))
@@ -76,7 +74,6 @@ test("titlebar forward is cleared after branching history from sidebar", async (
7674

7775
const third = page.locator(`[data-session-id="${c.id}"] a`).first()
7876
await expect(third).toBeVisible()
79-
await third.scrollIntoViewIfNeeded()
8077
await third.click()
8178

8279
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${c.id}(?:\\?|#|$)`))
@@ -102,7 +99,6 @@ test("keyboard shortcuts navigate titlebar history", async ({ page, slug, sdk, g
10299

103100
const link = page.locator(`[data-session-id="${two.id}"] a`).first()
104101
await expect(link).toBeVisible()
105-
await link.scrollIntoViewIfNeeded()
106102
await link.click()
107103

108104
await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))

packages/app/e2e/projects/project-edit.spec.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
import { test, expect } from "../fixtures"
2-
import { openSidebar } from "../actions"
2+
import { clickMenuItem, openProjectMenu, openSidebar } from "../actions"
33

44
test("dialog edit project updates name and startup script", async ({ page, withProject }) => {
55
await page.setViewportSize({ width: 1400, height: 800 })
66

7-
await withProject(async () => {
7+
await withProject(async ({ slug }) => {
88
await openSidebar(page)
99

1010
const open = async () => {
11-
const header = page.locator(".group\\/project").first()
12-
await header.hover()
13-
const trigger = header.getByRole("button", { name: "More options" }).first()
14-
await expect(trigger).toBeVisible()
15-
await trigger.click({ force: true })
16-
17-
const menu = page.locator('[data-component="dropdown-menu-content"]').first()
18-
await expect(menu).toBeVisible()
19-
20-
const editItem = menu.getByRole("menuitem", { name: "Edit" }).first()
21-
await expect(editItem).toBeVisible()
22-
await editItem.click({ force: true })
11+
const menu = await openProjectMenu(page, slug)
12+
await clickMenuItem(menu, /^Edit$/i, { force: true })
2313

2414
const dialog = page.getByRole("dialog")
2515
await expect(dialog).toBeVisible()

packages/app/e2e/projects/projects-switch.spec.ts

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,45 @@
11
import { base64Decode } from "@opencode-ai/util/encode"
2+
import type { Page } from "@playwright/test"
23
import { test, expect } from "../fixtures"
3-
import {
4-
defocus,
5-
createTestProject,
6-
cleanupTestProject,
7-
openSidebar,
8-
setWorkspacesEnabled,
9-
sessionIDFromUrl,
10-
} from "../actions"
4+
import { defocus, createTestProject, cleanupTestProject, openSidebar, sessionIDFromUrl } from "../actions"
115
import { projectSwitchSelector, promptSelector, workspaceItemSelector, workspaceNewSessionSelector } from "../selectors"
126
import { createSdk, dirSlug, sessionPath } from "../utils"
137

148
function slugFromUrl(url: string) {
159
return /\/([^/]+)\/session(?:\/|$)/.exec(url)?.[1] ?? ""
1610
}
1711

12+
async function workspaces(page: Page, directory: string, enabled: boolean) {
13+
await page.evaluate(
14+
({ directory, enabled }: { directory: string; enabled: boolean }) => {
15+
const key = "opencode.global.dat:layout"
16+
const raw = localStorage.getItem(key)
17+
const data = raw ? JSON.parse(raw) : {}
18+
const sidebar = data.sidebar && typeof data.sidebar === "object" ? data.sidebar : {}
19+
const current =
20+
sidebar.workspaces && typeof sidebar.workspaces === "object" && !Array.isArray(sidebar.workspaces)
21+
? sidebar.workspaces
22+
: {}
23+
const next = { ...current }
24+
25+
if (enabled) next[directory] = true
26+
if (!enabled) delete next[directory]
27+
28+
localStorage.setItem(
29+
key,
30+
JSON.stringify({
31+
...data,
32+
sidebar: {
33+
...sidebar,
34+
workspaces: next,
35+
},
36+
}),
37+
)
38+
},
39+
{ directory, enabled },
40+
)
41+
}
42+
1843
test("can switch between projects from sidebar", async ({ page, withProject }) => {
1944
await page.setViewportSize({ width: 1400, height: 800 })
2045

@@ -60,8 +85,11 @@ test("switching back to a project opens the latest workspace session", async ({
6085
async ({ directory, slug }) => {
6186
rootDir = directory
6287
await defocus(page)
88+
await workspaces(page, directory, true)
89+
await page.reload()
90+
await expect(page.locator(promptSelector)).toBeVisible()
6391
await openSidebar(page)
64-
await setWorkspacesEnabled(page, slug, true)
92+
await expect(page.getByRole("button", { name: "New workspace" }).first()).toBeVisible()
6593

6694
await page.getByRole("button", { name: "New workspace" }).first().click()
6795

packages/app/e2e/projects/workspaces.spec.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,6 @@ test("can reorder workspaces by drag and drop", async ({ page, withProject }) =>
336336
const src = page.locator(workspaceItemSelector(from)).first()
337337
const dst = page.locator(workspaceItemSelector(to)).first()
338338

339-
await src.scrollIntoViewIfNeeded()
340-
await dst.scrollIntoViewIfNeeded()
341-
342339
const a = await src.boundingBox()
343340
const b = await dst.boundingBox()
344341
if (!a || !b) throw new Error("Failed to resolve workspace drag bounds")

packages/app/e2e/settings/settings-keybinds.spec.ts

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,22 +32,19 @@ test("changing sidebar toggle keybind works", async ({ page, gotoSession }) => {
3232

3333
await closeDialog(page, dialog)
3434

35-
const main = page.locator("main")
36-
const initialClasses = (await main.getAttribute("class")) ?? ""
37-
const initiallyClosed = initialClasses.includes("xl:border-l")
35+
const button = page.getByRole("button", { name: /toggle sidebar/i }).first()
36+
const initiallyClosed = (await button.getAttribute("aria-expanded")) !== "true"
3837

3938
await page.keyboard.press(`${modKey}+Shift+H`)
40-
await page.waitForTimeout(100)
39+
await expect(button).toHaveAttribute("aria-expanded", initiallyClosed ? "true" : "false")
4140

42-
const afterToggleClasses = (await main.getAttribute("class")) ?? ""
43-
const afterToggleClosed = afterToggleClasses.includes("xl:border-l")
41+
const afterToggleClosed = (await button.getAttribute("aria-expanded")) !== "true"
4442
expect(afterToggleClosed).toBe(!initiallyClosed)
4543

4644
await page.keyboard.press(`${modKey}+Shift+H`)
47-
await page.waitForTimeout(100)
45+
await expect(button).toHaveAttribute("aria-expanded", initiallyClosed ? "false" : "true")
4846

49-
const finalClasses = (await main.getAttribute("class")) ?? ""
50-
const finalClosed = finalClasses.includes("xl:border-l")
47+
const finalClosed = (await button.getAttribute("aria-expanded")) !== "true"
5148
expect(finalClosed).toBe(initiallyClosed)
5249
})
5350

packages/app/e2e/sidebar/sidebar-popover-actions.spec.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { test, expect } from "../fixtures"
22
import { closeSidebar, hoverSessionItem } from "../actions"
3-
import { projectSwitchSelector, sessionItemSelector } from "../selectors"
3+
import { projectSwitchSelector } from "../selectors"
44

55
test("collapsed sidebar popover stays open when archiving a session", async ({ page, slug, sdk, gotoSession }) => {
66
const stamp = Date.now()
@@ -15,20 +15,23 @@ test("collapsed sidebar popover stays open when archiving a session", async ({ p
1515
await gotoSession(one.id)
1616
await closeSidebar(page)
1717

18+
const oneItem = page.locator(`[data-session-id="${one.id}"]`).last()
19+
const twoItem = page.locator(`[data-session-id="${two.id}"]`).last()
20+
1821
const project = page.locator(projectSwitchSelector(slug)).first()
1922
await expect(project).toBeVisible()
2023
await project.hover()
2124

22-
await expect(page.locator(sessionItemSelector(one.id)).first()).toBeVisible()
23-
await expect(page.locator(sessionItemSelector(two.id)).first()).toBeVisible()
25+
await expect(oneItem).toBeVisible()
26+
await expect(twoItem).toBeVisible()
2427

2528
const item = await hoverSessionItem(page, one.id)
2629
await item
2730
.getByRole("button", { name: /archive/i })
2831
.first()
2932
.click()
3033

31-
await expect(page.locator(sessionItemSelector(two.id)).first()).toBeVisible()
34+
await expect(twoItem).toBeVisible()
3235
} finally {
3336
await sdk.session.delete({ sessionID: one.id }).catch(() => undefined)
3437
await sdk.session.delete({ sessionID: two.id }).catch(() => undefined)

packages/app/e2e/sidebar/sidebar-session-links.spec.ts

Lines changed: 0 additions & 1 deletion

0 commit comments

Comments
 (0)