diff --git a/packages/app/e2e/app/home.spec.ts b/packages/app/e2e/app/home.spec.ts
index 3f65219fe..a727b9234 100644
--- a/packages/app/e2e/app/home.spec.ts
+++ b/packages/app/e2e/app/home.spec.ts
@@ -20,7 +20,7 @@ test("@smoke home renders the hero composer and starter cards", async ({ page, p
const home = page.locator('[data-component="session-new-home"]')
const composer = home.locator(sessionComposerDockSelector)
- const firstCard = home.getByRole("button", { name: /Process documents/i })
+ 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("button", { name: "Open project" }).first()).toBeVisible()
@@ -31,7 +31,7 @@ test("@smoke home renders the hero composer and starter cards", async ({ page, p
await expect(home.locator(promptSelector)).toBeVisible()
await expect(firstCard).toBeVisible()
await expect(page.getByRole("button", { name: /Analyze data/i })).toBeVisible()
- await expect(page.getByRole("button", { name: /Write faster/i })).toBeVisible()
+ await expect(page.getByRole("button", { name: /Start writing/i })).toBeVisible()
await expect(page.getByRole("button", { name: "Right utility panel" })).toBeVisible()
await expect(workspaceChip).toBeVisible()
diff --git a/packages/app/src/components/prompt-input/workspace-chip.tsx b/packages/app/src/components/prompt-input/workspace-chip.tsx
index 5e5bceca1..b6864faca 100644
--- a/packages/app/src/components/prompt-input/workspace-chip.tsx
+++ b/packages/app/src/components/prompt-input/workspace-chip.tsx
@@ -1,3 +1,4 @@
+import { Icon } from "@opencode-ai/ui/icon"
import { Popover } from "@opencode-ai/ui/popover"
import { base64Encode } from "@opencode-ai/util/encode"
import { getFilename } from "@opencode-ai/util/path"
@@ -60,9 +61,9 @@ export function WorkspaceChip() {
}}
trigger={
<>
-
+
{label()}
-
+
>
}
class="w-60 rounded-[10px] border border-border-strong bg-surface-base p-1 shadow-lg"
@@ -90,7 +91,7 @@ export function WorkspaceChip() {
setOpen(false)
}}
>
-
+
{getFilename(workspace)}
)
@@ -107,7 +108,7 @@ export function WorkspaceChip() {
layoutPage.openProject()
}}
>
-
+
{language.t("workspace.chip.add")}
@@ -116,36 +117,3 @@ export function WorkspaceChip() {
)
}
-function FolderIcon(props: { class?: string }) {
- return (
-
- )
-}
-
-function ChevronIcon(props: { class?: string }) {
- return (
-
- )
-}
-
-function PlusIcon(props: { class?: string }) {
- return (
-
- )
-}
diff --git a/packages/app/src/components/session/pawwork-skill-meta.ts b/packages/app/src/components/session/pawwork-skill-meta.ts
index bd775c080..1c43ac0ec 100644
--- a/packages/app/src/components/session/pawwork-skill-meta.ts
+++ b/packages/app/src/components/session/pawwork-skill-meta.ts
@@ -1,31 +1,45 @@
import type { IconProps } from "@opencode-ai/ui/icon"
-import type { SkillIconName } from "./skill-icons"
+import type { JSX } from "solid-js"
-export const pawworkSkillCards = [
+type PawworkSkillCard = {
+ readonly name: "document-processing" | "data-analysis" | "writing-assistant"
+ readonly iconName: IconProps["name"]
+ readonly homeIcon: IconProps["name"]
+ readonly homeIconClass: string
+ readonly homeIconStyle?: JSX.CSSProperties
+ readonly titleKey: string
+ readonly descriptionKey: string
+}
+
+// Writing accent (`#8B5FBF`) lives as inline style because Tailwind v4 is
+// configured with `--color-*: initial`, so palette utilities like
+// `text-violet-500` resolve to no CSS variable and render black.
+export const pawworkSkillCards: readonly PawworkSkillCard[] = [
{
name: "document-processing",
- iconName: "folder" as IconProps["name"],
- homeIcon: "folder" as SkillIconName,
+ iconName: "doc-processing",
+ homeIcon: "doc-processing",
homeIconClass: "text-icon-warning-base",
titleKey: "session.new.card.document.title",
descriptionKey: "session.new.card.document.description",
},
{
name: "data-analysis",
- iconName: "status" as IconProps["name"],
- homeIcon: "bar-chart" as SkillIconName,
+ iconName: "bar-chart",
+ homeIcon: "bar-chart",
homeIconClass: "text-icon-success-base",
titleKey: "session.new.card.analysis.title",
descriptionKey: "session.new.card.analysis.description",
},
{
name: "writing-assistant",
- iconName: "pencil-line" as IconProps["name"],
- homeIcon: "pencil" as SkillIconName,
- homeIconClass: "text-violet-500",
+ iconName: "pencil-line",
+ homeIcon: "pencil-line",
+ homeIconClass: "",
+ homeIconStyle: { color: "#8B5FBF" },
titleKey: "session.new.card.writing.title",
descriptionKey: "session.new.card.writing.description",
},
-] as const
+]
-export type PawworkSkillName = (typeof pawworkSkillCards)[number]["name"]
+export type PawworkSkillName = PawworkSkillCard["name"]
diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx
index 94a5c1a24..975debe45 100644
--- a/packages/app/src/components/session/session-new-view.tsx
+++ b/packages/app/src/components/session/session-new-view.tsx
@@ -1,7 +1,7 @@
+import { Icon } from "@opencode-ai/ui/icon"
import { For, createSignal, Show, type JSX } from "solid-js"
import { useLanguage } from "@/context/language"
import { pawworkSkillCards, type PawworkSkillName } from "./pawwork-skill-meta"
-import { SkillIcon } from "./skill-icons"
type ComposerCtx = {
onModeChange: (mode: "normal" | "shell") => void
@@ -23,7 +23,7 @@ export function NewSessionView(props: { composer?: (ctx: ComposerCtx) => JSX.Ele
{language.t("session.new.title")}
{language.t("session.new.subtitle")}
-
+
{(card) => {
const isSelected = () => mode() === "normal" && selectedSkill() === card.name
@@ -33,14 +33,19 @@ export function NewSessionView(props: { composer?: (ctx: ComposerCtx) => JSX.Ele
data-skill-card={card.name}
aria-pressed={isSelected()}
classList={{
- "flex items-center gap-2 rounded-xl border px-6 py-3 transition-colors": true,
+ "flex items-center justify-center gap-2 rounded-xl border px-6 py-3 transition-colors": true,
"border-border-weaker-base bg-surface-base hover:border-border-weak-base hover:bg-surface-raised-base-hover":
!isSelected(),
"border-border-interactive-base bg-surface-raised-base shadow-sm": isSelected(),
}}
onClick={() => toggleSkill(card.name)}
>
-
+
{language.t(card.titleKey)}
)
diff --git a/packages/app/src/components/session/skill-icons.tsx b/packages/app/src/components/session/skill-icons.tsx
deleted file mode 100644
index 44497f650..000000000
--- a/packages/app/src/components/session/skill-icons.tsx
+++ /dev/null
@@ -1,37 +0,0 @@
-import type { JSX } from "solid-js"
-
-const ICONS = {
- folder: [
- "M760 1850 c-119 -11 -182 -48 -220 -132 -18 -41 -20 -70 -20 -518 0 -474 0 -476 22 -520 16 -31 38 -54 72 -75 l49 -30 541 0 541 0 40 27 c22 15 51 44 65 65 l25 37 3 385 c3 417 0 441 -51 489 -48 46 -83 51 -342 59 l-246 6 -37 45 c-94 113 -114 131 -165 150 -56 20 -147 24 -277 12z m276 -107 c14 -9 50 -49 79 -88 83 -113 90 -115 377 -115 222 0 237 -1 261 -20 28 -22 43 -74 32 -109 -6 -21 -7 -21 -578 -21 -315 0 -578 4 -585 9 -11 6 -13 40 -10 150 l3 143 37 34 38 34 160 0 c133 0 165 -3 186 -17z m745 -454 c3 -6 5 -136 5 -289 l-1 -280 -27 -27 c-38 -38 -91 -41 -611 -42 -456 -1 -469 0 -512 49 -19 21 -20 36 -20 309 l0 286 110 5 c190 9 1049 -1 1056 -11z",
- ],
- "bar-chart": [
- "M1575 1872 c-22 -15 -50 -43 -62 -61 l-23 -34 0 -576 c0 -553 1 -577 20 -605 33 -51 86 -76 160 -76 79 0 134 27 162 80 17 33 18 65 16 615 l-3 580 -23 33 c-53 73 -173 95 -247 44z m161 -93 c18 -20 19 -46 22 -570 l2 -549 -24 -25 c-32 -32 -96 -35 -126 -5 -20 20 -20 33 -20 579 l0 560 22 15 c33 24 101 21 124 -5z",
- "M1110 1499 c-14 -6 -36 -20 -48 -32 -52 -48 -52 -49 -52 -459 0 -366 1 -384 20 -416 28 -45 89 -72 168 -72 69 0 103 13 142 55 l25 27 3 394 c3 442 3 440 -67 488 -31 22 -48 26 -102 25 -35 0 -75 -5 -89 -10z m146 -100 c18 -20 19 -41 19 -386 l-1 -365 -24 -20 c-31 -25 -106 -20 -127 8 -10 14 -13 98 -13 385 l0 368 22 15 c33 24 101 21 124 -5z",
- "M630 1133 c-19 -10 -45 -33 -57 -52 -22 -32 -23 -45 -25 -220 -1 -237 -1 -240 29 -280 33 -42 78 -61 149 -61 65 0 114 20 149 62 25 29 25 30 25 244 0 231 -3 245 -55 294 -23 21 -38 25 -103 28 -59 2 -85 -1 -112 -15z m151 -93 c18 -10 19 -23 19 -205 0 -189 -1 -194 -22 -209 -34 -24 -85 -20 -114 9 l-25 25 3 180 c3 165 5 180 23 194 23 18 87 21 116 6z",
- ],
- pencil: [
- "M1423 1865 c-67 -19 -144 -67 -178 -110 -17 -23 -34 -35 -50 -35 -14 0 -32 -11 -45 -27 -34 -44 -334 -347 -389 -393 -53 -45 -63 -63 -85 -155 -8 -33 -21 -87 -30 -120 -17 -59 -93 -386 -103 -436 -4 -20 2 -31 23 -48 16 -13 37 -21 49 -18 11 2 135 34 276 70 l256 65 229 233 c126 128 239 243 251 256 16 17 29 22 47 17 37 -9 69 31 61 75 -6 30 -2 39 33 75 42 43 82 110 82 136 0 9 5 21 11 27 19 19 5 122 -25 190 -34 74 -113 154 -181 183 -63 27 -166 34 -232 15z m171 -89 c64 -19 111 -57 146 -119 24 -42 30 -64 30 -115 0 -76 -14 -113 -67 -172 l-40 -44 -27 18 c-25 16 -29 16 -52 1 -27 -18 -31 -42 -9 -71 8 -10 15 -22 15 -26 0 -3 -71 -77 -157 -165 -87 -87 -197 -199 -244 -250 l-86 -91 -124 -31 c-68 -16 -146 -37 -174 -45 -27 -9 -53 -12 -57 -8 -5 5 8 29 28 55 37 47 39 72 9 83 -9 4 -31 -8 -56 -31 -68 -61 -69 -41 -11 182 29 109 52 206 52 216 0 9 8 25 19 35 10 9 110 108 223 220 138 136 212 202 226 202 14 0 27 11 36 29 26 50 107 111 176 132 40 12 93 10 144 -5z",
- "M1283 1618 c-13 -6 -23 -16 -23 -21 0 -14 200 -207 215 -207 16 0 35 21 35 40 0 17 -179 200 -195 199 -6 0 -20 -5 -32 -11z",
- "M1083 1188 c-128 -128 -236 -241 -239 -252 -9 -28 40 -70 65 -57 31 17 466 471 469 490 3 18 -31 51 -52 51 -6 0 -115 -105 -243 -232z",
- ],
-} as const
-
-export type SkillIconName = keyof typeof ICONS
-
-export function SkillIcon(props: { name: SkillIconName; class?: string }): JSX.Element {
- return (
-
- )
-}
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts
index 69f6941f1..3d5958639 100644
--- a/packages/app/src/i18n/en.ts
+++ b/packages/app/src/i18n/en.ts
@@ -616,12 +616,12 @@ export const dict = {
"session.new.title": "Choose what to do",
"session.new.subtitle": "PawWork helps you process files, analyze information, write content, and tackle everyday tasks.",
"session.new.reassurance": "Files and conversations stay on your computer",
- "session.new.card.document.title": "Process documents",
+ "session.new.card.document.title": "Process docs",
"session.new.card.document.description": "Edit, convert, and extract from Word, Excel, PowerPoint, and PDF files.",
"session.new.card.analysis.title": "Analyze data",
"session.new.card.analysis.description":
"Summarize spreadsheets, build charts, and answer questions from your files.",
- "session.new.card.writing.title": "Write faster",
+ "session.new.card.writing.title": "Start writing",
"session.new.card.writing.description": "Draft emails, reports, plans, and polished work copy.",
"session.new.placeholder.document": "Share a document and tell me what to organize, convert, or extract…",
"session.new.placeholder.analysis": "Share your data and tell me what to summarize, chart, or answer…",
diff --git a/packages/app/src/pages/layout/pawwork-sidebar.tsx b/packages/app/src/pages/layout/pawwork-sidebar.tsx
index 7d6b886b2..7f2979db8 100644
--- a/packages/app/src/pages/layout/pawwork-sidebar.tsx
+++ b/packages/app/src/pages/layout/pawwork-sidebar.tsx
@@ -2,6 +2,7 @@ import type { Session } from "@opencode-ai/sdk/v2/client"
import { Button } from "@opencode-ai/ui/button"
import { ContextMenu } from "@opencode-ai/ui/context-menu"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
+import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { createEffect, createMemo, createSignal, For, Show, type Accessor, type JSX } from "solid-js"
@@ -26,21 +27,6 @@ const FilterIcon = (props: { size?: number }) => {
)
}
-const PinIcon = (props: { filled?: boolean; size?: number }) => {
- const size = props.size ?? 16
- return (
-
- )
-}
-
export const PawworkSidebar = (props: {
scope?: "main" | "peek"
mobile?: boolean
@@ -145,12 +131,12 @@ export const PawworkSidebar = (props: {
}}
classList={{
"inline-flex size-6 items-center justify-center rounded transition-colors": true,
- "text-accent-brand opacity-100 pointer-events-auto": isPinned(),
+ "text-text-strong opacity-100 pointer-events-auto": isPinned(),
"text-text-weak opacity-0 pointer-events-none group-hover/session:opacity-100 group-hover/session:pointer-events-auto group-focus-within/session:opacity-100 group-focus-within/session:pointer-events-auto hover:text-text-base":
!isPinned(),
}}
>
-
+
)}
titleContent={({ session: rowSession, title }) => (
@@ -264,7 +250,7 @@ export const PawworkSidebar = (props: {
size="large"
variant="ghost"
icon="magnifying-glass"
- class="w-full [&>[data-component=icon]]:!size-5"
+ class="w-full"
onClick={props.onSearch}
>
{language.t("sidebar.pawwork.search")}
diff --git a/packages/app/src/pages/session/session-side-panel.tsx b/packages/app/src/pages/session/session-side-panel.tsx
index 5217a286b..520433b84 100644
--- a/packages/app/src/pages/session/session-side-panel.tsx
+++ b/packages/app/src/pages/session/session-side-panel.tsx
@@ -69,7 +69,7 @@ export function openReviewShellTab(sidePanel: { openTab: (tab: "review") => void
}
/** Maps right-panel tab names to their shell icon components. */
-function RightPanelShellIcon(props: { icon: ShellTabIcon }) {
+function RightPanelShellIcon(props: { icon: ShellTabIcon; active?: boolean }) {
return (
@@ -82,10 +82,10 @@ function RightPanelShellIcon(props: { icon: ShellTabIcon }) {
-
+
-
+
)
@@ -317,7 +317,7 @@ export function SessionSidePanel(props: {
closable={tab.closable}
onClose={view().sidePanel.closeTab}
>
-
+
{tab.label}
}
@@ -328,7 +328,7 @@ export function SessionSidePanel(props: {
closable={tab.closable}
onClose={view().sidePanel.closeTab}
>
-
+
{tab.label}
@@ -349,7 +349,7 @@ export function SessionSidePanel(props: {
openFilePicker(showAllFiles)}>
-
+
{language.t("command.file.open")}
{command.keybind("file.open")}
diff --git a/packages/ui/src/components/icon.tsx b/packages/ui/src/components/icon.tsx
index 08726d0ff..d748c3249 100644
--- a/packages/ui/src/components/icon.tsx
+++ b/packages/ui/src/components/icon.tsx
@@ -2,106 +2,111 @@ import { splitProps, type ComponentProps } from "solid-js"
const icons = {
"align-right": ``,
- "arrow-up": ``,
- "arrow-left": ``,
- "arrow-right": ``,
- archive: ``,
- "bubble-5": ``,
+ "arrow-up": ``,
+ "arrow-left": ``,
+ "arrow-right": ``,
+ archive: ``,
+ "bubble-5": ``,
prompt: ``,
- brain: ``,
+ brain: ``,
fork: ``,
- "bullet-list": ``,
- "check-small": ``,
- "chevron-down": ``,
- "chevron-left": ``,
- "chevron-right": ``,
- "chevron-grabber-vertical": ``,
+ "bullet-list": ``,
+ "check-small": ``,
+ "chevron-down": ``,
+ "chevron-left": ``,
+ "chevron-right": ``,
+ "chevron-grabber-vertical": ``,
"chevron-double-right": ``,
- "circle-x": ``,
- close: ``,
- "close-small": ``,
- checklist: ``,
- console: ``,
- terminal: ``,
- "terminal-active": `
-`,
- review: ``,
- "review-active": `
-`,
+ "circle-x": ``,
+ close: ``,
+ "close-small": ``,
+ checklist: ``,
+ console: ``,
+ terminal: ``,
+ "terminal-active": ``,
+ review: ``,
+ "review-active": ``,
expand: ``,
collapse: ``,
- code: ``,
- "code-lines": ``,
- "circle-ban-sign": ``,
+ code: ``,
+ "code-lines": ``,
+ "circle-ban-sign": ``,
"edit-small-2": ``,
eye: ``,
- enter: ``,
- folder: ``,
- "file-tree": ``,
- "file-tree-active": `
-`,
- "magnifying-glass": ``,
- "plus-small": ``,
- plus: ``,
- "new-session": ``,
- "new-session-active": `
-`,
- "pencil-line": ``,
- mcp: ``,
- glasses: ``,
- "magnifying-glass-menu": ``,
- "window-cursor": ``,
- task: ``,
- stop: ``,
- status: ``,
- "status-active": `
-
-`,
- sidebar: ``,
- "sidebar-active": `
-`,
+ enter: ``,
+ folder: ``,
+ "file-tree": ``,
+ "file-tree-active": ``,
+ "magnifying-glass": ``,
+ "plus-small": ``,
+ plus: ``,
+ "new-session": ``,
+ "new-session-active": ``,
+ "pencil-line": ``,
+ mcp: ``,
+ glasses: ``,
+ "magnifying-glass-menu": ``,
+ "window-cursor": ``,
+ task: ``,
+ stop: ``,
+ status: ``,
+ "status-active": ``,
+ sidebar: ``,
+ "sidebar-active": ``,
"layout-left": ``,
"layout-left-partial": ``,
"layout-left-full": ``,
"layout-right": ``,
"layout-right-partial": ``,
"layout-right-full": ``,
- "square-arrow-top-right": ``,
- "open-file": ``,
+ "square-arrow-top-right": ``,
+ "open-file": ``,
"speech-bubble": ``,
- comment: ``,
- "folder-add-left": ``,
- github: ``,
+ comment: ``,
+ "folder-add-left": ``,
+ github: ``,
discord: ``,
"layout-bottom": ``,
"layout-bottom-partial": ``,
"layout-bottom-full": ``,
- "dot-grid": ``,
- "circle-check": ``,
- copy: ``,
- check: ``,
+ "dot-grid": ``,
+ "circle-check": ``,
+ copy: ``,
+ check: ``,
photo: ``,
share: ``,
shield: ``,
- download: ``,
- menu: ``,
+ download: ``,
+ menu: ``,
server: ``,
- branch: ``,
- edit: ``,
- help: ``,
- "settings-gear": ``,
- dash: ``,
- "cloud-upload": ``,
- trash: ``,
- sliders: ``,
- keyboard: ``,
- selector: ``,
- "arrow-down-to-line": ``,
- warning: ``,
- reset: ``,
+ branch: ``,
+ edit: ``,
+ help: ``,
+ "settings-gear": ``,
+ dash: ``,
+ "cloud-upload": ``,
+ trash: ``,
+ sliders: ``,
+ keyboard: ``,
+ selector: ``,
+ "arrow-down-to-line": ``,
+ warning: ``,
+ reset: ``,
link: ``,
- providers: ``,
- models: ``,
+ providers: ``,
+ models: ``,
+
+ workspace: ``,
+ "doc-processing": ``,
+ "bar-chart": ``,
+ pin: ``,
+ "chevron-up": ``,
+ "plugin": ``,
+ "automation": ``,
+ "remote-control": ``,
+ "schedule": ``,
+ "key": ``,
+
}
export interface IconProps extends ComponentProps<"svg"> {
@@ -111,7 +116,6 @@ export interface IconProps extends ComponentProps<"svg"> {
export function Icon(props: IconProps) {
const [local, others] = splitProps(props, ["name", "size", "class", "classList"])
- const viewBox = () => (local.name === "magnifying-glass" ? "0 0 16 16" : "0 0 20 20")
return (