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
21 changes: 19 additions & 2 deletions packages/app/e2e/commands/panels.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { test, expect } from "../fixtures"
import { titlebarRightSelector } from "../selectors"
import { openSidebar } from "../actions"
import { pawworkSessionNewSelector, titlebarRightSelector } from "../selectors"
import { modKey } from "../utils"

test("desktop right-panel tabs switch between review and files within a unified utility shell", async ({ page, gotoSession }) => {
await gotoSession()

const rightToggle = page.locator(`${titlebarRightSelector} button`).first()
const rightPanel = page.locator("#right-panel")
const rightPanel = page.locator('[data-component="right-panel"]')
await expect(rightToggle).toBeVisible()
await expect(rightPanel).toHaveAttribute("aria-hidden", "true")

Expand Down Expand Up @@ -36,6 +37,22 @@ test("desktop right-panel tabs switch between review and files within a unified
await expect(reviewTab).toHaveAttribute("aria-selected", "true")
})

test("desktop remains clickable after right-panel resize ends with mouseup", async ({ page, gotoSession, slug }) => {
await gotoSession()
await openSidebar(page)

const rightPanel = page.locator("#right-panel")
await page.keyboard.press(`${modKey}+Shift+R`)
await expect(rightPanel).toHaveAttribute("aria-hidden", "false")

await page.locator('[data-component="right-panel-resize-wrapper"]').dispatchEvent("pointerdown")
await page.mouse.up()

await page.locator(pawworkSessionNewSelector).click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session(?:\\?|#|$)`))
await expect(page.locator('[data-component="session-new-home"]')).toBeVisible()
})

test("desktop session keeps a single right-panel toggle and icon-first utility tabs", async ({ page, gotoSession }) => {
await gotoSession()

Expand Down
22 changes: 21 additions & 1 deletion packages/app/e2e/sidebar/sidebar-session-links.spec.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import type { Page } from "@playwright/test"
import { test, expect } from "../fixtures"
import { cleanupSession, cleanupTestProject, createTestProject, openSidebar, waitSession } from "../actions"
import { promptSelector } from "../selectors"

async function expectUrlToStayMatched(page: Page, pattern: RegExp, stableFor = 300) {
let stableSince = Date.now()
await expect
.poll(() => {
if (!pattern.test(page.url())) {
stableSince = Date.now()
return false
}
return Date.now() - stableSince >= stableFor
})
.toBe(true)
}

test("sidebar session links navigate to the selected session", async ({ page, slug, sdk, gotoSession }) => {
const stamp = Date.now()

Expand All @@ -20,9 +34,15 @@ test("sidebar session links navigate to the selected session", async ({ page, sl
await expect(target).toBeVisible()
await target.click()

await expect(page).toHaveURL(new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`))
const selectedSessionUrl = new RegExp(`/${slug}/session/${two.id}(?:\\?|#|$)`)
await expect(page).toHaveURL(selectedSessionUrl)
await expectUrlToStayMatched(page, selectedSessionUrl)
await expect(page.locator(promptSelector)).toBeVisible()
await expect(page.locator(`[data-session-id="${two.id}"] a`).first()).toHaveClass(/\bactive\b/)

await page.locator('[data-action="pawwork-session-new"]').click()
await expect(page).toHaveURL(new RegExp(`/${slug}/session(?:\\?|#|$)`))
await expect(page.locator('[data-component="session-new-home"]')).toBeVisible()
} finally {
await cleanupSession({ sdk, sessionID: one.id })
await cleanupSession({ sdk, sessionID: two.id })
Expand Down
5 changes: 2 additions & 3 deletions packages/app/src/pages/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ import { PawworkTitlebar } from "./layout/pawwork-titlebar"
import { SettingsPage, type SettingsPageTab } from "@/components/settings-page"
import { DialogDeleteSession } from "@/components/dialog-delete-session"
import { sessionTitle } from "@/utils/session-title"
import { sizingStopEvents } from "@/pages/session/helpers"

export default function Layout(props: ParentProps) {
const [store, setStore, , ready] = persisted(
Expand Down Expand Up @@ -192,9 +193,7 @@ export default function Layout(props: ParentProps) {

onMount(() => {
const stop = () => setState("sizing", false)
makeEventListener(window, "pointerup", stop)
makeEventListener(window, "pointercancel", stop)
makeEventListener(window, "blur", stop)
for (const event of sizingStopEvents) makeEventListener(window, event, stop)
})

createEffect(() => {
Expand Down
7 changes: 7 additions & 0 deletions packages/app/src/pages/session/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
createSessionTabs,
focusTerminalById,
getTabReorderIndex,
sizingStopEvents,
shouldFocusTerminalOnKeyDown,
} from "./helpers"

Expand Down Expand Up @@ -117,6 +118,12 @@ describe("getTabReorderIndex", () => {
})
})

describe("createSizing", () => {
test("listens for mouse and touch endings as resize fallbacks", () => {
expect(sizingStopEvents).toEqual(["pointerup", "pointercancel", "mouseup", "touchend", "touchcancel", "blur"])
})
})

describe("createSessionTabs", () => {
test("normalizes the effective file tab", () => {
createRoot((dispose) => {
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/pages/session/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ export const getTabReorderIndex = (tabs: readonly string[], from: string, to: st
return toIndex
}

export const sizingStopEvents = ["pointerup", "pointercancel", "mouseup", "touchend", "touchcancel", "blur"] as const

export const createSizing = () => {
const [state, setState] = createStore({ active: false })
let t: number | undefined
Expand All @@ -174,9 +176,7 @@ export const createSizing = () => {
}

onMount(() => {
makeEventListener(window, "pointerup", stop)
makeEventListener(window, "pointercancel", stop)
makeEventListener(window, "blur", stop)
for (const event of sizingStopEvents) makeEventListener(window, event, stop)
})

onCleanup(() => {
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/pages/session/session-side-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,7 @@ export function SessionSidePanel(props: {
<Show when={isDesktop()}>
<aside
id="right-panel"
data-component="right-panel"
aria-label={language.t("session.panel.utility")}
aria-hidden={!open()}
inert={!open()}
Expand All @@ -275,7 +276,7 @@ export function SessionSidePanel(props: {
style={{ width: panelWidth() }}
>
<div
data-testid="right-panel-resize-wrapper"
data-component="right-panel-resize-wrapper"
onPointerDown={() => props.size.start()}
class="absolute top-0 left-0 h-full z-10"
>
Expand Down
39 changes: 38 additions & 1 deletion packages/desktop-electron/electron-builder-app-update.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { tmpdir } from "node:os"
import { join } from "node:path"

import type { Configuration } from "electron-builder"
import { createConfig, getPublishConfig } from "./electron-builder.config"
import {
createConfig,
getPublishConfig,
nativeWatcherFileSets,
nativeWatcherPackageNames,
} from "./electron-builder.config"
import { serializeAppUpdateConfig } from "./scripts/write-app-update-config"

const roots: string[] = []
Expand Down Expand Up @@ -74,6 +79,38 @@ describe("electron builder app-update config", () => {
)
})

test("native watcher package list covers desktop targets", () => {
expect(nativeWatcherPackageNames()).toEqual([
"@parcel/watcher-darwin-arm64",
"@parcel/watcher-darwin-x64",
"@parcel/watcher-linux-arm64-glibc",
"@parcel/watcher-linux-arm64-musl",
"@parcel/watcher-linux-x64-glibc",
"@parcel/watcher-linux-x64-musl",
"@parcel/watcher-win32-arm64",
"@parcel/watcher-win32-x64",
])
})

test("packages native file watcher bindings for the embedded server", () => {
const config = createConfig("prod")
const resources = nativeWatcherFileSets()

expect(config.extraResources).toEqual(
expect.arrayContaining(
resources.map((resource) =>
expect.objectContaining({
from: resource.from,
to: resource.to,
}),
),
),
)
expect(resources.map((resource) => resource.to)).toEqual(
nativeWatcherPackageNames().map((packageName) => join("node_modules", ...packageName.split("/"))),
)
})

test("afterPack writes app-update.yml to the packager-reported macOS resources path", async () => {
const root = mkdtempSync(join(tmpdir(), "pawwork-builder-config-"))
roots.push(root)
Expand Down
51 changes: 51 additions & 0 deletions packages/desktop-electron/electron-builder.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { execFile } from "node:child_process"
import { mkdir, writeFile } from "node:fs/promises"
import { createRequire } from "node:module"
import path from "node:path"
import { fileURLToPath } from "node:url"
import { promisify } from "node:util"
Expand All @@ -10,6 +11,11 @@ import { writeAppUpdateConfig, type GitHubPublishConfig } from "./scripts/write-
const execFileAsync = promisify(execFile)
const rootDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..")
const signScript = path.join(rootDir, "script", "sign-windows.ps1")
const requireFromOpencode = createRequire(path.join(rootDir, "packages", "opencode", "package.json"))
const opencodePackage = requireFromOpencode("./package.json") as {
dependencies?: Record<string, string>
devDependencies?: Record<string, string>
}
type Channel = "dev" | "beta" | "prod"
const localizedMacDisplayNameByChannel: Record<Channel, string> = {
dev: "爪印 Dev",
Expand All @@ -34,6 +40,50 @@ function currentChannel(): Channel {
return "dev"
}

const nativeWatcherPackages = [
"@parcel/watcher-darwin-arm64",
"@parcel/watcher-darwin-x64",
"@parcel/watcher-linux-arm64-glibc",
"@parcel/watcher-linux-arm64-musl",
"@parcel/watcher-linux-x64-glibc",
"@parcel/watcher-linux-x64-musl",
"@parcel/watcher-win32-arm64",
"@parcel/watcher-win32-x64",
] as const

export function nativeWatcherPackageNames() {
return [...nativeWatcherPackages]
}

function bunPackageFallbackDir(packageName: string) {
const version = opencodePackage.devDependencies?.[packageName] ?? opencodePackage.dependencies?.[packageName]
if (!version) throw new Error(`Missing ${packageName} in packages/opencode/package.json`)
return path.join(
rootDir,
"node_modules",
".bun",
`${packageName.replace("/", "+")}@${version}`,
"node_modules",
...packageName.split("/"),
)
}

function nativeWatcherPackageDir(packageName: string) {
try {
return path.dirname(requireFromOpencode.resolve(`${packageName}/package.json`))
} catch {
return bunPackageFallbackDir(packageName)
}
}

export function nativeWatcherFileSets() {
return nativeWatcherPackages.map((packageName) => ({
from: nativeWatcherPackageDir(packageName),
to: path.join("node_modules", ...packageName.split("/")),
filter: ["**/*"],
}))
}

export function getPublishConfig(channel: Channel): GitHubPublishConfig | undefined {
if (channel === "beta") return { provider: "github", owner: "Astro-Han", repo: "pawwork-beta", channel: "latest" }
if (channel === "prod") return { provider: "github", owner: "Astro-Han", repo: "pawwork", channel: "latest" }
Expand All @@ -59,6 +109,7 @@ const getBase = (): Configuration => ({
},
files: ["out/**/*", "resources/**/*"],
extraResources: [
...nativeWatcherFileSets(),
{
from: path.join(rootDir, "skills"),
to: "skills",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ import { resolveOpencodeRoot } from "./embedded-server-path"

const opencodeRoot = resolveOpencodeRoot(import.meta.dir)

await $`bun install --cwd ${opencodeRoot} --os="*" --cpu="*" --frozen-lockfile`
Comment thread
Astro-Han marked this conversation as resolved.
await $`bun run --cwd ${opencodeRoot} build:embedded-server`
Loading