Просмотр исходного кода

fix(desktop): context menu button / tab intermittent issue (#34420)

Co-authored-by: Brendan Allan <git@brendonovich.dev>
usrnk1 2 месяцев назад
Родитель
Сommit
003c22b4a3

+ 33 - 13
packages/app/src/components/session-context-usage.tsx

@@ -26,7 +26,7 @@ function openSessionContext(args: {
   layout: ReturnType<typeof useLayout>
   tabs: ReturnType<ReturnType<typeof useLayout>["tabs"]>
 }) {
-  if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
+  args.view.reviewPanel.open(args.view.reviewPanel.opened() ? "other" : "context-button")
   if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
   void args.tabs.open("context")
   args.tabs.setActive("context")
@@ -64,16 +64,21 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
   const cost = createMemo(() => {
     return usd().format(info()?.cost ?? 0)
   })
+  const contextVisible = createMemo(() => view().reviewPanel.opened() && tabState.activeTab() === "context")
+  const hasOtherTabs = createMemo(() => tabs().all().some((tab) => tab !== "context" && tab !== "review"))
 
   const openContext = () => {
     if (!params.id) return
 
-    if (tabState.activeTab() === "context") {
+    const sessionView = view()
+    if (contextVisible()) {
       tabs().close("context")
+      if (sessionView.reviewPanel.source() === "context-button" && !hasOtherTabs()) sessionView.reviewPanel.close()
       return
     }
+
     openSessionContext({
-      view: view(),
+      view: sessionView,
       layout,
       tabs: tabs(),
     })
@@ -81,7 +86,20 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
 
   const circle = () => (
     <div class="flex items-center justify-center">
-      <ProgressCircle size={16} strokeWidth={2} percentage={context()?.usage ?? 0} />
+      <ProgressCircle
+        size={16}
+        strokeWidth={2}
+        percentage={context()?.usage ?? 0}
+        style={
+          variant() === "indicator"
+            ? {
+                "--progress-circle-background": "var(--v2-background-bg-layer-04, var(--border-weak-base))",
+                "--progress-circle-background-overlay": "var(--v2-overlay-simple-overlay-pressed, transparent)",
+                "--progress-circle-progress": "var(--v2-icon-icon-base, var(--icon-base))",
+              }
+            : undefined
+        }
+      />
     </div>
   )
   const circleV2 = () => (
@@ -119,10 +137,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
 
   return (
     <Show when={params.id}>
-      <Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
-        <Switch>
-          <Match when={variant() === "indicator"}>{circle()}</Match>
-          <Match when={buttonAppearance() === "v2"}>
+      <Switch>
+        <Match when={variant() === "indicator"}>{circle()}</Match>
+        <Match when={buttonAppearance() === "v2"}>
+          <Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
             <IconButtonV2
               type="button"
               variant="ghost-muted"
@@ -131,8 +149,10 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
               onClick={openContext}
               aria-label={language.t("context.usage.view")}
             />
-          </Match>
-          <Match when={true}>
+          </Tooltip>
+        </Match>
+        <Match when={true}>
+          <Tooltip value={tooltipValue()} placement={props.placement ?? "top"}>
             <Button
               type="button"
               variant="ghost"
@@ -142,9 +162,9 @@ export function SessionContextUsage(props: SessionContextUsageProps) {
             >
               {circle()}
             </Button>
-          </Match>
-        </Switch>
-      </Tooltip>
+          </Tooltip>
+        </Match>
+      </Switch>
     </Show>
   )
 }

+ 24 - 8
packages/app/src/context/layout.tsx

@@ -78,6 +78,7 @@ export type LocalProject = Partial<Project> & { worktree: string; expanded: bool
 export type HomeProjectSelection = { server: ServerConnection.Key; directory?: string }
 
 export type ReviewDiffStyle = "unified" | "split"
+export type ReviewPanelSource = "context-button" | "other"
 
 export type LayoutRoute =
   | { type: "home" }
@@ -304,6 +305,9 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         },
       }),
     )
+    const [ephemeral, setEphemeral] = createStore({
+      reviewPanelSource: "other" as ReviewPanelSource,
+    })
 
     const MAX_SESSION_KEYS = 50
     const PENDING_MESSAGE_TTL_MS = 2 * 60 * 1000
@@ -780,6 +784,7 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
         const s = createMemo(() => store.sessionView[key()] ?? { scroll: {} })
         const terminalOpened = createMemo(() => store.terminal?.opened ?? false)
         const reviewPanelOpened = createMemo(() => store.review?.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED)
+        const reviewPanelSource = createMemo(() => (reviewPanelOpened() ? ephemeral.reviewPanelSource : "other"))
 
         function setTerminalOpened(next: boolean) {
           const current = store.terminal
@@ -793,16 +798,26 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
           setStore("terminal", "opened", next)
         }
 
-        function setReviewPanelOpened(next: boolean) {
+        function setReviewPanelOpened(next: boolean, source: ReviewPanelSource) {
+          const nextSource = next ? source : "other"
           const current = store.review
           if (!current) {
-            setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
+            batch(() => {
+              setStore("review", { diffStyle: "split" as ReviewDiffStyle, panelOpened: next })
+              setEphemeral("reviewPanelSource", nextSource)
+            })
             return
           }
 
           const value = current.panelOpened ?? DEFAULT_REVIEW_PANEL_OPENED
-          if (value === next) return
-          setStore("review", "panelOpened", next)
+          if (value === next) {
+            if (ephemeral.reviewPanelSource !== nextSource) setEphemeral("reviewPanelSource", nextSource)
+            return
+          }
+          batch(() => {
+            setStore("review", "panelOpened", next)
+            setEphemeral("reviewPanelSource", nextSource)
+          })
         }
 
         return {
@@ -838,14 +853,15 @@ export const { use: useLayout, provider: LayoutProvider } = createSimpleContext(
           },
           reviewPanel: {
             opened: reviewPanelOpened,
-            open() {
-              setReviewPanelOpened(true)
+            source: reviewPanelSource,
+            open(source: ReviewPanelSource = "other") {
+              setReviewPanelOpened(true, source)
             },
             close() {
-              setReviewPanelOpened(false)
+              setReviewPanelOpened(false, "other")
             },
             toggle() {
-              setReviewPanelOpened(!reviewPanelOpened())
+              setReviewPanelOpened(!reviewPanelOpened(), "other")
             },
           },
           review: {

+ 6 - 2
packages/ui/src/components/progress-circle.css

@@ -2,11 +2,15 @@
   transform: rotate(-90deg);
 
   [data-slot="progress-circle-background"] {
-    stroke: var(--border-weak-base);
+    stroke: var(--progress-circle-background, var(--border-weak-base));
+  }
+
+  [data-slot="progress-circle-background-overlay"] {
+    stroke: var(--progress-circle-background-overlay, transparent);
   }
 
   [data-slot="progress-circle-progress"] {
-    stroke: var(--border-active);
+    stroke: var(--progress-circle-progress, var(--border-active));
     transition: stroke-dashoffset 0.35s cubic-bezier(0.65, 0, 0.35, 1);
   }
 }

+ 8 - 1
packages/ui/src/components/progress-circle.tsx

@@ -1,6 +1,6 @@
 import { type ComponentProps, createMemo, splitProps } from "solid-js"
 
-export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList"> {
+export interface ProgressCircleProps extends Pick<ComponentProps<"svg">, "class" | "classList" | "style"> {
   percentage: number
   size?: number
   strokeWidth?: number
@@ -43,6 +43,13 @@ export function ProgressCircle(props: ProgressCircleProps) {
         data-slot="progress-circle-background"
         stroke-width={strokeWidth()}
       />
+      <circle
+        cx={center}
+        cy={center}
+        r={radius()}
+        data-slot="progress-circle-background-overlay"
+        stroke-width={strokeWidth()}
+      />
       <circle
         cx={center}
         cy={center}