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
2 changes: 0 additions & 2 deletions packages/app/e2e/app/shell-frame.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,6 @@ test("@smoke shell frame exposes stable desktop hooks", async ({ page, gotoSessi
await expect(page.locator(titlebarCenterSelector)).toContainText(/new session/i)
await expect(page.locator(`${titlebarRightSelector} button`).first()).toBeVisible()
await expect(page.getByRole("button", { name: /toggle sidebar/i }).first()).toBeVisible()
await expect(page.getByRole("button", { name: /navigate back/i })).toBeVisible()
await expect(page.getByRole("button", { name: /navigate forward/i })).toBeVisible()

const settings = await openSettings(page)
await expect(settings.getByRole("heading", { level: 2 })).toBeVisible()
Expand Down
25 changes: 15 additions & 10 deletions packages/app/public/oc-theme-preload.js
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
// Phase 1 ships only the `pawwork` theme, which is locked to light mode.
// When a dark-capable theme is bundled, this file must gain a theme-aware
// guard so `pawwork-color-scheme` is forced to "light" ONLY for light-only
// themes; tracked in issue #23.
;(function () {
var key = "pawwork-theme-id"
var themeKey = "pawwork-theme-id"
var schemeKey = "pawwork-color-scheme"
var cssLightKey = "pawwork-theme-css-light"
var cssDarkKey = "pawwork-theme-css-dark"
var scheme = "light"

try {
var storedTheme = localStorage.getItem(key)

var storedTheme = localStorage.getItem(themeKey)
if (storedTheme !== "pawwork") {
localStorage.setItem(key, "pawwork")
localStorage.setItem(themeKey, "pawwork")
localStorage.removeItem(cssLightKey)
localStorage.removeItem(cssDarkKey)
}
localStorage.setItem(schemeKey, "light")

var storedScheme = localStorage.getItem(schemeKey)
if (storedScheme === "light" || storedScheme === "dark") {
scheme = storedScheme
} else if (storedScheme === "system") {
scheme =
window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light"
} else {
localStorage.setItem(schemeKey, "light")
}
} catch (_err) {
// Private mode / blocked storage / non-browser environment: the app still
// needs the dataset attributes below so the first paint is not unstyled.
}

document.documentElement.dataset.theme = "pawwork"
document.documentElement.dataset.colorScheme = "light"
document.documentElement.dataset.colorScheme = scheme
})()
40 changes: 2 additions & 38 deletions packages/app/src/components/titlebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { useLocation, useNavigate, useParams } from "@solidjs/router"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Icon } from "@opencode-ai/ui/icon"
import { Button } from "@opencode-ai/ui/button"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"

import { useLayout } from "@/context/layout"
import { usePlatform } from "@/context/platform"
Expand Down Expand Up @@ -52,10 +52,6 @@ export function Titlebar() {
})
})

const canBack = createMemo(() => history.index > 0)
const canForward = createMemo(() => history.index < history.stack.length - 1)
const hasProjects = createMemo(() => layout.projects.list().length > 0)

const back = () => {
const next = backPath(history)
if (!next) return
Expand Down Expand Up @@ -148,7 +144,7 @@ export function Titlebar() {
<div class="hidden xl:flex items-center shrink-0">
<Show when={params.dir}>
<div
class="flex items-center shrink-0 w-8 mr-1"
class="flex items-center shrink-0 w-8"
aria-hidden={layout.sidebar.opened() ? "true" : undefined}
>
<div
Expand Down Expand Up @@ -181,38 +177,6 @@ export function Titlebar() {
</div>
</div>
</Show>
<Show when={hasProjects()}>
<div
class="flex items-center gap-0 transition-transform"
classList={{
"translate-x-0": !layout.sidebar.opened(),
"-translate-x-[36px]": layout.sidebar.opened(),
"duration-180 ease-out": !layout.sidebar.opened(),
"duration-180 ease-in": layout.sidebar.opened(),
}}
>
<Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-left"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canBack()}
onClick={back}
aria-label={language.t("common.goBack")}
/>
</Tooltip>
<Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
<Button
variant="ghost"
icon="chevron-right"
class="titlebar-icon w-6 h-6 p-0 box-border"
disabled={!canForward()}
onClick={forward}
aria-label={language.t("common.goForward")}
/>
</Tooltip>
</div>
</Show>
</div>
</div>
<div id="opencode-titlebar-left" data-shell-slot="left-portal" class="flex items-center gap-3 min-w-0 px-2" />
Expand Down
38 changes: 28 additions & 10 deletions packages/app/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -78,36 +78,54 @@
--shell-titlebar-height: 44px;
--shell-frame-margin: 12px;
--shell-frame-radius: 12px;
--shell-background-base: #F4EFE9;
--shell-background-weak: #EEE6DE;
--shell-surface-base: #FBF8F4;
--shell-surface-raised: #F7F1EA;
--shell-surface-strong: #FEFBF8;
--shell-border-base: #D8CFC6;
--shell-border-weak: #E7DED6;
--shell-background-base: #F8F7F6;
--shell-background-weak: #F0EFEE;
--shell-surface-base: #FAF9F9;
--shell-surface-raised: #F8F7F7;
--shell-surface-strong: #FCFBFB;
--shell-border-base: #D6D2CA;
--shell-border-weak: #E3DED6;
--shell-highlight-overlay: radial-gradient(circle at top, rgb(255 255 255 / 0.72), transparent 52%);
--shell-frame-border: color-mix(in srgb, var(--shell-border-base) 78%, #c4b5a8 22%);
--shell-frame-shadow:
0 24px 64px -36px rgb(33 26 22 / 0.34),
0 10px 28px -20px rgb(33 26 22 / 0.2);
--shell-frame-surface: linear-gradient(180deg, var(--shell-surface-strong) 0%, var(--shell-surface-base) 100%);
--shell-titlebar-border: color-mix(in srgb, var(--shell-border-weak) 72%, var(--shell-border-base) 28%);

background:
radial-gradient(circle at top, rgb(255 255 255 / 0.72), transparent 52%),
var(--shell-highlight-overlay),
linear-gradient(180deg, var(--shell-background-base), var(--shell-background-weak));
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

:root[data-color-scheme="dark"] [data-component="desktop-shell"][data-platform="desktop"] {
--shell-background-base: #1E2128;
--shell-background-weak: #181A20;
--shell-surface-base: #23252C;
--shell-surface-raised: #25272E;
--shell-surface-strong: #2A2C34;
--shell-border-base: #2F323A;
--shell-border-weak: #23252A;
--shell-highlight-overlay: radial-gradient(circle at top, rgb(255 255 255 / 0.04), transparent 52%);
--shell-frame-border: color-mix(in srgb, var(--shell-border-base) 78%, #5a5e68 22%);
--shell-frame-shadow:
0 24px 64px -36px rgb(0 0 0 / 0.6),
0 10px 28px -20px rgb(0 0 0 / 0.45);
--shell-titlebar-border: color-mix(in srgb, var(--shell-border-weak) 72%, var(--shell-border-base) 28%);
}

:root {
--accent-brand: #ff5910;
--accent-brand-hover: #e04e0e;
--accent-brand-on: #ffffff;
--sidebar: #f2f1ee;
--sidebar: #FAF9F9;
}

:root[data-color-scheme="dark"] {
--accent-brand: #ff5910;
--accent-brand-hover: #ff7a3a;
--accent-brand-on: #ffffff;
--sidebar: #1d1d1b;
--sidebar: #23252A;
}

[data-component="titlebar-shell"][data-platform="desktop"][data-os="macos"] {
Expand Down
15 changes: 13 additions & 2 deletions packages/app/src/pages/layout/pawwork-sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -238,11 +238,22 @@ export const PawworkSidebar = (props: {
<section
data-component="pawwork-sidebar"
data-sidebar-scope={props.scope ?? "main"}
class="flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-background-base"
class="flex h-full min-h-0 min-w-0 flex-1 flex-col overflow-hidden bg-background-weak"
>
<div class="shrink-0 px-3 pt-3">
<div class="flex flex-col gap-2">
<Button data-action="pawwork-session-new" size="large" icon="new-session" class="w-full" onClick={props.onNew}>
<Button
data-action="pawwork-session-new"
size="large"
icon="new-session"
class="w-full"
style={{
"--button-secondary-base": "var(--surface-interactive-base)",
"--button-secondary-hover": "var(--surface-interactive-hover)",
"--icon-base": "var(--accent-brand)",
}}
onClick={props.onNew}
>
{language.t("command.session.new")}
</Button>
<Button
Expand Down
42 changes: 27 additions & 15 deletions packages/app/src/theme-preload.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,41 +4,53 @@ const src = await Bun.file(new URL("../public/oc-theme-preload.js", import.meta.

const run = () => Function(src)()

const setMatchMedia = (prefersDark: boolean) => {
Object.defineProperty(window, "matchMedia", {
value: () => ({ matches: prefersDark }) as MediaQueryList,
configurable: true,
})
}

beforeEach(() => {
document.head.innerHTML = ""
document.documentElement.removeAttribute("data-theme")
document.documentElement.removeAttribute("data-color-scheme")
localStorage.clear()
Object.defineProperty(window, "matchMedia", {
value: () =>
({
matches: false,
}) as MediaQueryList,
configurable: true,
})
setMatchMedia(false)
})

describe("theme preload", () => {
test("uses PawWork as the empty-storage default theme", () => {
test("defaults first-install users to pawwork light", () => {
run()
expect(document.documentElement.dataset.theme).toBe("pawwork")
expect(document.documentElement.dataset.colorScheme).toBe("light")
expect(localStorage.getItem("pawwork-color-scheme")).toBe("light")
})

test("locks PawWork to light and rewrites stored color scheme", () => {
test("preserves stored dark color scheme on pawwork", () => {
localStorage.setItem("pawwork-theme-id", "pawwork")
localStorage.setItem("pawwork-color-scheme", "dark")

run()

expect(document.documentElement.dataset.theme).toBe("pawwork")
expect(document.documentElement.dataset.colorScheme).toBe("light")
expect(localStorage.getItem("pawwork-theme-id")).toBe("pawwork")
expect(localStorage.getItem("pawwork-color-scheme")).toBe("light")
expect(document.documentElement.dataset.colorScheme).toBe("dark")
expect(localStorage.getItem("pawwork-color-scheme")).toBe("dark")
})

test("resolves 'system' scheme against prefers-color-scheme", () => {
localStorage.setItem("pawwork-theme-id", "pawwork")
localStorage.setItem("pawwork-color-scheme", "system")
setMatchMedia(true)

run()

expect(document.documentElement.dataset.colorScheme).toBe("dark")
expect(localStorage.getItem("pawwork-color-scheme")).toBe("system")
})

for (const legacy of ["oc-1", "oc-2", "dracula", "nightowl", "amoled"]) {
test(`migrates legacy "${legacy}" theme to pawwork and clears cached css`, () => {
test(`migrates legacy "${legacy}" theme to pawwork and clears cached css, preserving scheme`, () => {
localStorage.setItem("pawwork-theme-id", legacy)
localStorage.setItem("pawwork-color-scheme", "dark")
localStorage.setItem("pawwork-theme-css-light", "--background-base:#ffffff;")
Expand All @@ -47,9 +59,9 @@ describe("theme preload", () => {
run()

expect(document.documentElement.dataset.theme).toBe("pawwork")
expect(document.documentElement.dataset.colorScheme).toBe("light")
expect(document.documentElement.dataset.colorScheme).toBe("dark")
expect(localStorage.getItem("pawwork-theme-id")).toBe("pawwork")
expect(localStorage.getItem("pawwork-color-scheme")).toBe("light")
expect(localStorage.getItem("pawwork-color-scheme")).toBe("dark")
expect(localStorage.getItem("pawwork-theme-css-light")).toBeNull()
expect(localStorage.getItem("pawwork-theme-css-dark")).toBeNull()
})
Expand Down
Loading
Loading