-
+
+
+
+ {language.t("session.share.popover.title")}
+
+
+ {shareUrl()
+ ? language.t("session.share.popover.description.shared")
+ : language.t("session.share.popover.description.unshared")}
+
+
+
+
+ {shareMutation.isPending
+ ? language.t("session.share.action.publishing")
+ : language.t("session.share.action.publish")}
+
+ }
+ >
+
+
+
+
+
+
+
+
+
+
+ }
+ >
+
+
{language.t("session.share.popover.title")}
-
+
{shareUrl()
? language.t("session.share.popover.description.shared")
: language.t("session.share.popover.description.unshared")}
-
+
+
}
>
-
-
-
+
+
{unshareMutation.isPending
? language.t("session.share.action.unpublishing")
: language.t("session.share.action.unpublish")}
-
-
+
-
+
diff --git a/packages/ui/src/v2/components/button-v2.css b/packages/ui/src/v2/components/button-v2.css
index 822904d0e3ce..4e5edad9edc4 100644
--- a/packages/ui/src/v2/components/button-v2.css
+++ b/packages/ui/src/v2/components/button-v2.css
@@ -113,6 +113,26 @@
cursor: not-allowed;
}
+/* Outline */
+[data-component="button-v2"][data-variant="outline"] {
+ background-color: transparent;
+ color: var(--v2-text-text-base);
+ box-shadow: inset 0 0 0 1px var(--v2-border-border-muted);
+}
+
+[data-component="button-v2"][data-variant="outline"]:is(:hover, [data-state="hover"]):not(:disabled) {
+ background-color: var(--v2-overlay-simple-overlay-hover);
+}
+
+[data-component="button-v2"][data-variant="outline"]:is(:active, [data-state="pressed"]):not(:disabled) {
+ background-color: var(--v2-overlay-simple-overlay-pressed);
+}
+
+[data-component="button-v2"][data-variant="outline"]:is(:disabled, [data-state="disabled"]) {
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
/* Contrast */
[data-component="button-v2"][data-variant="contrast"] {
background-image:
diff --git a/packages/ui/src/v2/components/button-v2.tsx b/packages/ui/src/v2/components/button-v2.tsx
index eef46b9c4a2f..0900d46d09a6 100644
--- a/packages/ui/src/v2/components/button-v2.tsx
+++ b/packages/ui/src/v2/components/button-v2.tsx
@@ -7,7 +7,7 @@ export interface ButtonV2Props
extends ComponentProps
,
Pick, "class" | "classList" | "children"> {
size?: "small" | "normal" | "large"
- variant?: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted"
+ variant?: "neutral" | "danger" | "contrast" | "ghost" | "ghost-muted" | "outline"
icon?: IconProps["name"]
}
diff --git a/packages/ui/src/v2/components/dialog-v2.css b/packages/ui/src/v2/components/dialog-v2.css
index 15b52d9482d3..b74d2723972b 100644
--- a/packages/ui/src/v2/components/dialog-v2.css
+++ b/packages/ui/src/v2/components/dialog-v2.css
@@ -59,6 +59,7 @@
margin: 0;
flex: 1;
min-width: 0;
+ user-select: none;
font-weight: 530;
font-size: 13px;
line-height: 16px;
@@ -80,6 +81,7 @@
margin: 0;
flex: none;
flex-grow: 0;
+ user-select: none;
font-weight: 530;
font-size: 15px;
line-height: 20px;
@@ -91,6 +93,7 @@
[data-slot="dialog-description"] {
flex: none;
flex-grow: 0;
+ user-select: none;
font-weight: 440;
font-size: 13px;
line-height: 20px;
diff --git a/packages/ui/src/v2/components/dialog-v2.tsx b/packages/ui/src/v2/components/dialog-v2.tsx
index c62dd43c7487..06821f0ffe0f 100644
--- a/packages/ui/src/v2/components/dialog-v2.tsx
+++ b/packages/ui/src/v2/components/dialog-v2.tsx
@@ -110,3 +110,5 @@ export function Dialog(props: DialogProps) {
)
}
+
+export const DialogV2 = Dialog
diff --git a/packages/ui/src/v2/components/icon.tsx b/packages/ui/src/v2/components/icon.tsx
index f986624eace9..63486caac10b 100644
--- a/packages/ui/src/v2/components/icon.tsx
+++ b/packages/ui/src/v2/components/icon.tsx
@@ -85,6 +85,14 @@ const icons = {
viewBox: "0 0 16 16",
body: `
`,
},
+ "outline-copy": {
+ viewBox: "0 0 16 16",
+ body: `
`,
+ },
+ "outline-square-arrow": {
+ viewBox: "0 0 16 16",
+ body: `
`,
+ },
archive: {
viewBox: "0 0 16 16",
body: `
`,
diff --git a/packages/ui/src/v2/components/progress-circle-v2.css b/packages/ui/src/v2/components/progress-circle-v2.css
new file mode 100644
index 000000000000..2f84ed4c2958
--- /dev/null
+++ b/packages/ui/src/v2/components/progress-circle-v2.css
@@ -0,0 +1,13 @@
+[data-component="progress-circle-v2"] {
+ display: block;
+ transform: rotate(-90deg);
+}
+
+[data-component="progress-circle-v2"] [data-slot="progress-circle-v2-background"] {
+ stroke: var(--v2-background-bg-layer-04);
+}
+
+[data-component="progress-circle-v2"] [data-slot="progress-circle-v2-progress"] {
+ stroke: var(--v2-icon-icon-muted);
+ transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
+}
diff --git a/packages/ui/src/v2/components/progress-circle-v2.tsx b/packages/ui/src/v2/components/progress-circle-v2.tsx
new file mode 100644
index 000000000000..473824d23746
--- /dev/null
+++ b/packages/ui/src/v2/components/progress-circle-v2.tsx
@@ -0,0 +1,46 @@
+import { type ComponentProps, createMemo, splitProps } from "solid-js"
+import "./progress-circle-v2.css"
+
+export interface ProgressCircleV2Props extends Pick
, "class" | "classList"> {
+ percentage: number
+ size?: number
+ strokeWidth?: number
+}
+
+export function ProgressCircleV2(props: ProgressCircleV2Props) {
+ const [split, rest] = splitProps(props, ["percentage", "size", "strokeWidth", "class", "classList"])
+
+ const size = () => split.size ?? 14
+ const strokeWidth = () => split.strokeWidth ?? 1.5
+ const viewBoxSize = 14
+ const center = viewBoxSize / 2
+ const radius = () => center - strokeWidth() / 2
+ const circumference = createMemo(() => 2 * Math.PI * radius())
+ const offset = createMemo(() => circumference() * (1 - Math.max(0, Math.min(100, split.percentage || 0)) / 100))
+
+ return (
+
+ )
+}
From 48dc05eee754426ce001b285d4c0b740ca0b803f Mon Sep 17 00:00:00 2001
From: "opencode-agent[bot]"
Date: Mon, 29 Jun 2026 10:13:23 +0000
Subject: [PATCH 008/303] chore: generate
---
packages/ui/src/v2/components/progress-circle-v2.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/packages/ui/src/v2/components/progress-circle-v2.tsx b/packages/ui/src/v2/components/progress-circle-v2.tsx
index 473824d23746..32393446ab06 100644
--- a/packages/ui/src/v2/components/progress-circle-v2.tsx
+++ b/packages/ui/src/v2/components/progress-circle-v2.tsx
@@ -31,7 +31,13 @@ export function ProgressCircleV2(props: ProgressCircleV2Props) {
[split.class ?? ""]: !!split.class,
}}
>
-
+
Date: Mon, 29 Jun 2026 15:50:26 +0530
Subject: [PATCH 009/303] feat(app): improve projects sidebar reactivity
(#34391)
Co-authored-by: Brendan Allan
---
packages/app/src/index.css | 62 ++++++++++++++++++++++++
packages/app/src/pages/home.tsx | 84 ++++++++++++++++++---------------
2 files changed, 108 insertions(+), 38 deletions(-)
diff --git a/packages/app/src/index.css b/packages/app/src/index.css
index 7b8435c04e3d..6f8ec89d4caf 100644
--- a/packages/app/src/index.css
+++ b/packages/app/src/index.css
@@ -171,4 +171,66 @@
transform: rotate(360deg);
}
}
+
+ @keyframes home-projects-fade-top {
+ from {
+ visibility: hidden;
+ }
+ to {
+ visibility: visible;
+ }
+ }
+
+ @keyframes home-projects-fade-bottom {
+ from {
+ visibility: visible;
+ }
+ to {
+ visibility: hidden;
+ }
+ }
+
+ [data-slot="home-projects-scroll"] {
+ timeline-scope: --home-projects-scroll;
+ }
+
+ [data-slot="home-projects-scroll"]::before,
+ [data-slot="home-projects-scroll"]::after {
+ content: "";
+ position: absolute;
+ left: 0;
+ right: 0;
+ z-index: 10;
+ height: 16px;
+ pointer-events: none;
+ visibility: hidden;
+ }
+
+ [data-slot="home-projects-scroll"]::before {
+ top: 0;
+ background: linear-gradient(to bottom, var(--v2-background-bg-base), transparent);
+ }
+
+ [data-slot="home-projects-scroll"]::after {
+ bottom: 0;
+ background: linear-gradient(to top, var(--v2-background-bg-base), transparent);
+ }
+
+ @supports (animation-timeline: --home-projects-scroll) and (timeline-scope: --home-projects-scroll) {
+ [data-slot="home-projects-scroll"] .scroll-view__viewport {
+ scroll-timeline: --home-projects-scroll y;
+ }
+
+ [data-slot="home-projects-scroll"]::before {
+ animation: home-projects-fade-top linear both;
+ animation-timeline: --home-projects-scroll;
+ animation-range: 0 0.1px;
+ }
+
+ [data-slot="home-projects-scroll"]::after {
+ animation: home-projects-fade-bottom linear both;
+ animation-timeline: --home-projects-scroll;
+ animation-range: calc(100% - 1.1px) calc(100% - 1px);
+ }
+ }
}
diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx
index b20427bf88eb..21c7d7137be3 100644
--- a/packages/app/src/pages/home.tsx
+++ b/packages/app/src/pages/home.tsx
@@ -668,10 +668,10 @@ function HomeProjectColumn(props: {
return (
diff --git a/packages/session-ui/src/components/message-part.tsx b/packages/session-ui/src/components/message-part.tsx
index 4af05c2584a8..17d3a697a7a7 100644
--- a/packages/session-ui/src/components/message-part.tsx
+++ b/packages/session-ui/src/components/message-part.tsx
@@ -11,6 +11,7 @@ import {
onCleanup,
Index,
type JSX,
+ type ComponentProps,
} from "solid-js"
import { createStore } from "solid-js/store"
import stripAnsi from "strip-ansi"
@@ -49,6 +50,8 @@ import { getDirectory as _getDirectory, getFilename } from "@opencode-ai/core/ut
import { checksum } from "@opencode-ai/core/util/encode"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { IconButton } from "@opencode-ai/ui/icon-button"
+import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
+import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { Spinner } from "@opencode-ai/ui/spinner"
import { TextShimmer } from "@opencode-ai/ui/text-shimmer"
import { AnimatedCountList } from "./tool-count-summary"
@@ -161,6 +164,7 @@ export interface MessageProps {
actions?: UserActions
showAssistantCopyPartID?: string | null
showReasoningSummaries?: boolean
+ useV2Actions?: boolean
}
export type SessionAction = (input: { sessionID: string; messageID: string }) => Promise