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: 2 additions & 2 deletions packages/app/e2e/app/home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ test("@smoke root route renders seeded home entrypoints", async ({ page }) => {
const home = page.locator('[data-component="session-new-home"]')

await expect(home).toBeVisible()
await expect(page.getByRole("heading", { name: "Choose what to do" })).toBeVisible()
await expect(page.getByRole("heading", { name: "What do you want to do?" })).toBeVisible()
await expect(home.locator(sessionComposerDockSelector)).toHaveCount(1)
await openSidebar(page)
await expect(page.getByRole("button", { name: "New session" })).toBeVisible()
Expand All @@ -24,7 +24,7 @@ test("@smoke home renders the hero composer and starter cards", async ({ page, p
const firstCard = home.getByRole("button", { name: /Process docs/i })
const workspaceChip = page.getByRole("button", { name: /Switch workspace|切换工作目录/i })
await expect(home).toBeVisible()
await expect(page.getByRole("heading", { name: "Choose what to do" })).toBeVisible()
await expect(page.getByRole("heading", { name: "What do you want to do?" })).toBeVisible()
await expect(page.locator(sessionComposerDockSelector)).toHaveCount(1)
await expect(composer).toHaveCount(1)
await expect(composer).toHaveCSS("text-align", "left")
Expand Down
20 changes: 13 additions & 7 deletions packages/app/src/components/prompt-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1127,7 +1127,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
as="div"
variant="ghost"
size="normal"
class="h-[28px]! min-w-0 w-44 px-1.5 justify-start! text-13-regular! text-text-base group rounded-xl! transition-colors hover:bg-surface-base-hover"
class="h-[28px]! min-w-0 px-1.5 justify-start! text-13-regular! text-text-base group rounded-xl! transition-colors hover:bg-surface-base-hover"
style={triggerStyle()}
onClick={() => {
void import("@/components/dialog-select-model-unpaid").then((x) => {
Expand All @@ -1142,7 +1142,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
/>
</Show>
<span class="flex-1 truncate text-center">
<span
class="truncate text-center max-w-[7rem] transition-[max-width] duration-200 ease-out"
classList={{ "@max-[28rem]/composer:max-w-0": !!local.model.current()?.provider?.id }}
>
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
</span>
<Icon name="chevron-down" size="small" class="shrink-0" />
Expand All @@ -1164,7 +1167,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
size: "normal",
style: triggerStyle(),
class:
"h-[28px]! min-w-0 w-44 px-1.5 justify-start! text-13-regular! text-text-base group rounded-xl! transition-colors hover:bg-surface-base-hover",
"h-[28px]! min-w-0 px-1.5 justify-start! text-13-regular! text-text-base group rounded-xl! transition-colors hover:bg-surface-base-hover",
"data-action": "prompt-model",
}}
onClose={restoreFocus}
Expand All @@ -1176,7 +1179,10 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
style={{ "will-change": "opacity", transform: "translateZ(0)" }}
/>
</Show>
<span class="flex-1 truncate text-center">
<span
class="truncate text-center max-w-[7rem] transition-[max-width] duration-200 ease-out"
classList={{ "@max-[28rem]/composer:max-w-0": !!local.model.current()?.provider?.id }}
>
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
</span>
<Icon name="chevron-down" size="small" class="shrink-0" />
Expand Down Expand Up @@ -1208,7 +1214,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
"aria-haspopup": "menu",
style: triggerStyle(),
class:
"h-[28px] px-2 max-w-[160px] inline-flex items-center gap-1.5 rounded-xl text-13-regular text-text-base transition-colors hover:bg-surface-base-hover",
"h-[28px] px-2 max-w-[160px] @max-[20rem]/composer:max-w-[80px] inline-flex items-center gap-1.5 rounded-xl text-13-regular text-text-base transition-[max-width,colors] duration-200 ease-out hover:bg-surface-base-hover",
} as any
}
trigger={
Expand Down Expand Up @@ -1436,7 +1442,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<DockShellForm
onSubmit={handleSubmit}
classList={{
"group/prompt-input": true,
"group/prompt-input @container/composer": true,
"border-icon-info-active border-dashed": store.draggingType !== null,
[props.class ?? ""]: !!props.class,
}}
Expand Down Expand Up @@ -1550,7 +1556,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}}
/>

<div class="pointer-events-none absolute inset-x-4 bottom-3 flex items-center justify-between gap-8">
<div class="pointer-events-none absolute inset-x-4 bottom-3 flex items-center justify-between gap-3">
<div
aria-hidden={store.mode !== "normal"}
class="pointer-events-auto flex min-w-0 items-center gap-2"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export function WorkspaceChip(props: { style?: JSX.CSSProperties | string } = {}
trigger={
<>
<Icon name="folder" size="small" class="shrink-0 text-text-weak" />
<span class="max-w-[120px] truncate">{label()}</span>
<span class="max-w-[120px] truncate transition-[max-width] duration-200 ease-out @max-[24rem]/composer:max-w-0">{label()}</span>
<Icon name="chevron-down" size="small" class="shrink-0 text-text-weak" />
</>
}
Expand Down
27 changes: 2 additions & 25 deletions packages/app/src/components/titlebar.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { createEffect, createMemo, Show, untrack } from "solid-js"
import { createStore } from "solid-js/store"
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 { TooltipKeybind } from "@opencode-ai/ui/tooltip"
Expand Down Expand Up @@ -101,32 +100,10 @@ export function Titlebar() {
>
<Show when={mac()}>
<div class="h-full shrink-0" style={{ width: `${72 / zoom()}px` }} />
<div class="xl:hidden w-10 shrink-0 flex items-center justify-center">
<IconButton
icon={layout.mobileSidebar.opened() ? "sidebar-active" : "sidebar"}
variant="ghost"
class="titlebar-icon rounded-md"
onClick={layout.mobileSidebar.toggle}
aria-label={language.t("sidebar.menu.toggle")}
aria-expanded={layout.mobileSidebar.opened()}
/>
</div>
</Show>
<Show when={!mac()}>
<div class="xl:hidden w-[48px] shrink-0 flex items-center justify-center">
<IconButton
icon={layout.mobileSidebar.opened() ? "sidebar-active" : "sidebar"}
variant="ghost"
class="titlebar-icon rounded-md"
onClick={layout.mobileSidebar.toggle}
aria-label={language.t("sidebar.menu.toggle")}
aria-expanded={layout.mobileSidebar.opened()}
/>
</div>
</Show>
<div class="flex items-center gap-1 shrink-0">
<TooltipKeybind
class={web() ? "hidden xl:flex shrink-0 ml-14" : "hidden xl:flex shrink-0 ml-2"}
class={web() ? "flex shrink-0 ml-14" : "flex shrink-0 ml-2"}
placement="bottom"
title={language.t("command.sidebar.toggle")}
keybind={command.keybind("sidebar.toggle")}
Expand All @@ -150,7 +127,7 @@ export function Titlebar() {
<div
class="transition-opacity duration-120 ease-out opacity-100"
classList={{
"xl:opacity-0 xl:ease-in xl:delay-0 xl:pointer-events-none": layout.sidebar.opened(),
"opacity-0 ease-in delay-0 pointer-events-none": layout.sidebar.opened(),
}}
>
<TooltipKeybind
Expand Down
17 changes: 17 additions & 0 deletions packages/app/src/context/layout.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,4 +175,21 @@ describe("migrateStoredLayout", () => {
expect(migrated.rightPanel.opened).toBe(false)
expect(migrated.rightPanel.width).toBe(DEFAULT_RIGHT_PANEL_WIDTH)
})

test("strips legacy mobileSidebar key while preserving sibling fields", () => {
const migrated = migrateStoredLayout({
mobileSidebar: { opened: true },
sidebar: { opened: true, width: 280 },
rightPanel: { opened: false, width: 380 },
sessionView: {},
sessionTabs: {},
}) as Record<string, unknown> & {
sidebar: { opened: boolean; width: number }
rightPanel: { opened: boolean; width: number }
}

expect("mobileSidebar" in migrated).toBe(false)
expect(migrated.sidebar).toEqual({ opened: true, width: 280 })
expect(migrated.rightPanel).toEqual({ opened: false, width: 380 })
})
})
23 changes: 6 additions & 17 deletions packages/app/src/context/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -198,9 +198,6 @@ export function createDefaultLayoutState() {
width: DEFAULT_RIGHT_PANEL_WIDTH,
opened: false,
},
mobileSidebar: {
opened: false,
},
sessionTabs: {} as Record<string, SessionTabs>,
sessionView: {} as Record<string, SessionView>,
handoff: {
Expand Down Expand Up @@ -293,19 +290,23 @@ export function migrateStoredLayout(value: unknown) {
const sessionViewMigration = migrateSessionView(value.sessionView, migratedSessionTabs)
const sessionStateChanged = sessionViewMigration.changed

const hasMobileSidebar = "mobileSidebar" in value

if (
migratedSidebar === sidebar &&
migratedReview === review &&
migratedFileTree === fileTree &&
migratedRightPanel === rightPanel &&
migratedSessionTabs === sessionTabs &&
!sessionStateChanged
!sessionStateChanged &&
!hasMobileSidebar
) {
return value
}

const { mobileSidebar: _mobileSidebar, ...rest } = value as Record<string, unknown>
return {
...value,
...rest,
sidebar: migratedSidebar,
Comment thread
Astro-Han marked this conversation as resolved.
review: migratedReview,
fileTree: migratedFileTree,
Expand Down Expand Up @@ -763,18 +764,6 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
setStore("rightPanel", "width", clampRightPanelWidth(width))
},
},
mobileSidebar: {
opened: createMemo(() => store.mobileSidebar?.opened ?? false),
show() {
setStore("mobileSidebar", "opened", true)
},
hide() {
setStore("mobileSidebar", "opened", false)
},
toggle() {
setStore("mobileSidebar", "opened", (x) => !x)
},
},
pendingMessage: {
set(sessionKey: string, messageID: string) {
const at = Date.now()
Expand Down
13 changes: 4 additions & 9 deletions packages/app/src/i18n/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,10 +632,7 @@ export const dict = {
"session.revertDock.expand": "Expand rolled back messages",
"session.revertDock.restore": "Restore message",

"session.new.title": "Choose what to do",
// Kept for parity/branding tests; UI usage removed in #300.
"session.new.subtitle":
"PawWork helps you process files, analyze information, write content, and tackle everyday tasks.",
"session.new.title": "What do you want to do?",
"session.new.reassurance": "Files and conversations stay on your computer",
"session.new.card.document.title": "Process docs",
"session.new.card.document.description": "Edit, convert, and extract from Word, Excel, PowerPoint, and PDF files.",
Expand Down Expand Up @@ -756,11 +753,10 @@ export const dict = {
"common.unknown": "unknown",

"common.time.justNow": "Just now",
"common.time.minutesAgo.short": "{{count}}m ago",
"common.time.hoursAgo.short": "{{count}}h ago",
"common.time.daysAgo.short": "{{count}}d ago",
"common.time.minutesAgo.short": "{{count}}m",
"common.time.hoursAgo.short": "{{count}}h",
"common.time.daysAgo.short": "{{count}}d",

"sidebar.menu.toggle": "Toggle menu",
"sidebar.nav.projectsAndSessions": "Projects and sessions",
"sidebar.settings": "Settings",
"sidebar.workspaces.enable": "Enable workspaces",
Expand All @@ -775,7 +771,6 @@ export const dict = {
"sidebar.empty.description": "Open a project to get started",
"sidebar.pawwork.search": "Search",
"sidebar.pawwork.empty.description": "Open a project to start using the session-first sidebar.",
"sidebar.pawwork.empty.sessions": "No sessions yet. Start a new one to begin.",
"sidebar.pawwork.pinned": "Pinned",
"sidebar.pawwork.all": "All sessions",
"sidebar.pawwork.pinSession": "Pin session",
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/i18n/parity.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const keys = [
"command.session.previous.unseen",
"command.session.next.unseen",
"session.new.title",
"session.new.subtitle",
"session.new.card.document.title",
"session.new.card.document.description",
"session.new.card.analysis.title",
Expand Down
2 changes: 0 additions & 2 deletions packages/app/src/i18n/zh-branding.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { dict as zh } from "./zh"
describe("zh branding copy", () => {
test("uses Chinese product naming on key user-facing surfaces", () => {
expect(zh["dialog.model.unpaid.freeModels.title"]).toBe("爪印内置免费模型")
expect(zh["session.new.subtitle"]).toBe("爪印可以帮你处理文件、分析信息、撰写内容并完成各类任务。")
expect(zh["sidebar.gettingStarted.line1"]).toBe("爪印内置免费模型,你可以立即开始使用。")
expect(zh["app.name.desktop"]).toBe("爪印")
expect(zh["toast.update.description"]).toBe("爪印有新版本 ({{version}}) 可安装。")
Expand All @@ -14,7 +13,6 @@ describe("zh branding copy", () => {
test("removes standalone PawWork from curated Chinese UI strings", () => {
const curatedKeys = [
"dialog.model.unpaid.freeModels.title",
"session.new.subtitle",
"sidebar.gettingStarted.line1",
"app.name.desktop",
"toast.update.description",
Expand Down
10 changes: 3 additions & 7 deletions packages/app/src/i18n/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -590,8 +590,6 @@ export const dict = {
"session.revertDock.expand": "展开已回滚消息",
"session.revertDock.restore": "恢复消息",
"session.new.title": "今天想做什么?",
// Kept for parity/branding tests; UI usage removed in #300.
"session.new.subtitle": "爪印可以帮你处理文件、分析信息、撰写内容并完成各类任务。",
"session.new.reassurance": "文件和对话仅在本机处理",
"session.new.card.document.title": "处理文档",
"session.new.card.document.description": "编辑、转换并提取 Word、Excel、PowerPoint 和 PDF 文件内容。",
Expand Down Expand Up @@ -674,7 +672,6 @@ export const dict = {
"common.loadMore": "加载更多",
"common.key.esc": "ESC",

"sidebar.menu.toggle": "切换菜单",
"sidebar.nav.projectsAndSessions": "项目和会话",
"sidebar.settings": "设置",
"sidebar.workspaces.enable": "启用工作区",
Expand All @@ -689,7 +686,6 @@ export const dict = {
"sidebar.empty.description": "打开一个项目开始使用",
"sidebar.pawwork.search": "搜索",
"sidebar.pawwork.empty.description": "打开一个项目后,即可使用以会话为中心的侧边栏。",
"sidebar.pawwork.empty.sessions": "还没有会话,先新建一个开始。",
"sidebar.pawwork.pinned": "已置顶",
"sidebar.pawwork.all": "全部会话",
"sidebar.pawwork.pinSession": "置顶会话",
Expand Down Expand Up @@ -983,9 +979,9 @@ export const dict = {
"error.server.invalidConfiguration": "配置无效",
"common.moreCountSuffix": " (还有 {{count}} 个)",
"common.time.justNow": "刚刚",
"common.time.minutesAgo.short": "{{count}}分钟前",
"common.time.hoursAgo.short": "{{count}}小时前",
"common.time.daysAgo.short": "{{count}}天前",
"common.time.minutesAgo.short": "{{count}}分钟",
"common.time.hoursAgo.short": "{{count}}小时",
"common.time.daysAgo.short": "{{count}}",
"settings.providers.connected.environmentDescription": "已通过环境变量连接",
"settings.providers.custom.description": "通过基础 URL 添加与 OpenAI 兼容的提供商。",

Expand Down
32 changes: 12 additions & 20 deletions packages/app/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -109,15 +109,20 @@
}

/* Mac native unified-chrome pattern: titlebar bg matches whatever surface
sits below it on each side. Left of --sidebar-width = sidebar bg (seamless
vertical extension of the sidebar panel up through the titlebar). Right
= main bg. The only visual seam is the sidebar/main vertical divider,
which now runs continuously from window top to bottom.
The xl+ gradient form lives inside the existing 1280px media block below
to satisfy shell-frame-contract.test.ts CSS rule ordering. */
sits below it. Left of --sidebar-width = sidebar bg (seamless vertical
extension of the sidebar panel up through the titlebar). Right = main
bg. Right-panel boundary is intentionally NOT drawn into the titlebar
(Mail / Finder / Notes do not split their toolbar) so it never overlaps
with breadcrumb or center-portal content when the panel is wide. */
[data-component="titlebar-shell"][data-platform="desktop"][data-os="macos"] {
padding-inline: 8px;
background: var(--background-base);
background: linear-gradient(
to right,
var(--background-weak) 0,
var(--background-weak) var(--sidebar-width, 0px),
var(--background-base) var(--sidebar-width, 0px),
var(--background-base) 100%
);
}

[data-component="titlebar-shell"][data-platform="desktop"][data-os="macos"] [data-shell-slot="left-portal"] {
Expand Down Expand Up @@ -169,18 +174,5 @@
overflow: hidden;
}

[data-component="titlebar-shell"][data-platform="desktop"][data-os="macos"] {
background: linear-gradient(
to right,
var(--background-weak) 0,
var(--background-weak) var(--sidebar-width, 0px),
var(--background-base) var(--sidebar-width, 0px),
var(--background-base) calc(100% - var(--right-panel-width, 0px)),
var(--right-panel-divider, transparent) calc(100% - var(--right-panel-width, 0px)),
var(--right-panel-divider, transparent) calc(100% - var(--right-panel-width, 0px) + 1px),
var(--background-base) calc(100% - var(--right-panel-width, 0px) + 1px),
var(--background-base) 100%
);
}
}
}
Loading
Loading