diff --git a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx
index 5f6865fb1..64f195471 100644
--- a/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx
+++ b/packages/ui/src/components/instance/shell/right-panel/RightPanel.tsx
@@ -1,4 +1,5 @@
import {
+ For,
Show,
Suspense,
createEffect,
@@ -11,10 +12,19 @@ import {
} from "solid-js"
import type { ToolState } from "@opencode-ai/sdk/v2"
import type { FileContent, FileNode } from "@opencode-ai/sdk/v2/client"
+import {
+ DragDropProvider,
+ DragDropSensors,
+ SortableProvider,
+ closestCenter,
+ createSortable,
+ type DragEvent as SolidDndDragEvent,
+} from "@thisbeyond/solid-dnd"
import IconButton from "@suid/material/IconButton"
import MenuOpenIcon from "@suid/icons-material/MenuOpen"
import PushPinIcon from "@suid/icons-material/PushPin"
import PushPinOutlinedIcon from "@suid/icons-material/PushPinOutlined"
+import { Settings2 } from "lucide-solid"
import type { Instance } from "../../../../types/instance"
import type { BackgroundProcess } from "../../../../../../server/src/api-types"
@@ -35,7 +45,7 @@ import { requestData } from "../../../../lib/opencode-api"
import { serverApi } from "../../../../lib/api-client"
import { showConfirmDialog } from "../../../../stores/alerts"
import { showToastNotification } from "../../../../lib/notifications"
-import { writeClientLayoutValue } from "../../../../stores/client-state"
+import { readClientLayoutValue, writeClientLayoutValue } from "../../../../stores/client-state"
import { useGlobalPointerDrag } from "../useGlobalPointerDrag"
import { useGitChanges } from "./useGitChanges"
import {
@@ -53,12 +63,24 @@ import {
RIGHT_PANEL_GIT_CHANGES_SPLIT_WIDTH_KEY,
RIGHT_PANEL_GIT_CHANGES_UNSTAGED_OPEN_NONPHONE_KEY,
RIGHT_PANEL_GIT_CHANGES_UNSTAGED_OPEN_PHONE_KEY,
+ RIGHT_PANEL_CUSTOMIZATION_STORAGE_KEY,
RIGHT_PANEL_TAB_STORAGE_KEY,
readStoredBool,
readStoredEnum,
readStoredPanelWidth,
readStoredRightPanelTab,
} from "../storage"
+import {
+ applyRightPanelItemCustomization,
+ collectRightPanelItems,
+ parseRightPanelCustomization,
+ setRightPanelItemHidden,
+ type RightPanelCustomization,
+ type RightPanelModule,
+ type RightPanelSectionModule,
+ type RightPanelTabModule,
+} from "./registry"
+import { CORE_STATUS_SECTION_ITEMS } from "./tabs/status-sections"
const LazyGitChangesTab = lazy(() => import("./tabs/GitChangesTab"))
const LazyFilesTab = lazy(() => import("./tabs/FilesTab"))
@@ -68,6 +90,32 @@ function RightPanelTabFallback() {
return
}
+interface SortableRightPanelTabProps {
+ tab: RightPanelTabModule
+ active: boolean
+ label: string
+ dragTitle: string
+ onSelect: () => void
+}
+
+const SortableRightPanelTab: Component = (props) => {
+ const sortable = createSortable(props.tab.id)
+ return (
+
+
+
+ )
+}
+
interface RightPanelProps {
t: (key: string, vars?: Record) => string
@@ -98,8 +146,12 @@ interface RightPanelProps {
const RightPanel: Component = (props) => {
const [rightPanelTab, setRightPanelTab] = createSignal(readStoredRightPanelTab("git-changes"))
- const defaultStatusSectionIds = ["provider-usage", "yolo-mode", "plan", "background-processes", "mcp", "lsp", "plugins"]
+ const defaultStatusSectionIds = CORE_STATUS_SECTION_ITEMS.map((section) => section.id)
const [rightPanelExpandedItems, setRightPanelExpandedItems] = createSignal(defaultStatusSectionIds)
+ const [rightPanelCustomizationOpen, setRightPanelCustomizationOpen] = createSignal(false)
+ const [rightPanelCustomization, setRightPanelCustomization] = createSignal(
+ parseRightPanelCustomization(readClientLayoutValue(RIGHT_PANEL_CUSTOMIZATION_STORAGE_KEY)),
+ )
const [browserPath, setBrowserPath] = createSignal(".")
const [browserEntries, setBrowserEntries] = createSignal(null)
@@ -648,77 +700,42 @@ const RightPanel: Component = (props) => {
setRightPanelExpandedItems(values)
}
- const tabClass = (tab: RightPanelTab) =>
- `right-panel-tab ${rightPanelTab() === tab ? "right-panel-tab-active" : "right-panel-tab-inactive"}`
+ const updateRightPanelCustomization = (updater: (current: RightPanelCustomization) => RightPanelCustomization) => {
+ const next = updater(rightPanelCustomization())
+ setRightPanelCustomization(next)
+ writeClientLayoutValue(RIGHT_PANEL_CUSTOMIZATION_STORAGE_KEY, JSON.stringify(next))
+ }
- return (
-
-
-
-
-
-
-
-
-
-
- (props.rightPinned() ? props.onUnpinRightDrawer() : props.onPinRightDrawer())}
- >
- {props.rightPinned() ? : }
-
-
-
-
-
-
+ const handleTabDragEnd = ({ draggable, droppable }: SolidDndDragEvent) => {
+ if (!droppable) return
+ moveTab(String(draggable.id), String(droppable.id))
+ }
-
-
- }>
+ const rightPanelModules = createMemo(() => [
+ {
+ id: "core-right-panel",
+ tabs: [
+ {
+ id: "git-changes",
+ labelKey: "instanceShell.rightPanel.tabs.gitChanges",
+ order: 10,
+ render: () => (
= (props) => {
onResizeTouchStart={handleSplitResizeTouchStart("git-changes")}
isPhoneLayout={props.isPhoneLayout}
/>
-
-
-
-
- }>
+ ),
+ },
+ {
+ id: "files",
+ labelKey: "instanceShell.rightPanel.tabs.files",
+ order: 20,
+ render: () => (
= (props) => {
onResizeTouchStart={handleSplitResizeTouchStart("files")}
isPhoneLayout={props.isPhoneLayout}
/>
-
-
-
-
- }>
+ ),
+ },
+ {
+ id: "status",
+ labelKey: "instanceShell.rightPanel.tabs.status",
+ order: 30,
+ render: () => (
= (props) => {
onTerminateBackgroundProcess={props.onTerminateBackgroundProcess}
expandedItems={rightPanelExpandedItems}
onExpandedItemsChange={handleAccordionChange}
+ customization={rightPanelCustomization}
+ onCustomizationChange={updateRightPanelCustomization}
+ extraSections={extraStatusSections()}
/>
-
+ ),
+ },
+ ],
+ },
+ ])
+
+ const allRightPanelTabs = createMemo(() => collectRightPanelItems(rightPanelModules(), "tabs"))
+ const visibleRightPanelTabs = createMemo(() =>
+ applyRightPanelItemCustomization(
+ allRightPanelTabs(),
+ rightPanelCustomization().tabOrder,
+ rightPanelCustomization().hiddenTabIds,
+ ),
+ )
+ const orderedRightPanelTabs = createMemo(() => applyRightPanelItemCustomization(allRightPanelTabs(), rightPanelCustomization().tabOrder, []))
+ const extraStatusSections = createMemo(() => collectRightPanelItems(rightPanelModules(), "statusSections"))
+ const allStatusSections = createMemo(() => [...CORE_STATUS_SECTION_ITEMS, ...extraStatusSections()])
+ const orderedStatusSections = createMemo(() => applyRightPanelItemCustomization(allStatusSections(), rightPanelCustomization().statusSectionOrder, []))
+ const visibleStatusSections = createMemo(() =>
+ applyRightPanelItemCustomization(
+ allStatusSections(),
+ rightPanelCustomization().statusSectionOrder,
+ rightPanelCustomization().hiddenStatusSectionIds,
+ ),
+ )
+ const activeRightPanelTab = createMemo(() => visibleRightPanelTabs().find((tab) => tab.id === rightPanelTab()) ?? visibleRightPanelTabs()[0])
+
+ createEffect(() => {
+ const active = activeRightPanelTab()
+ if (active && active.id !== rightPanelTab()) {
+ setRightPanelTab(active.id)
+ }
+ })
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ (props.rightPinned() ? props.onUnpinRightDrawer() : props.onPinRightDrawer())}
+ >
+ {props.rightPinned() ? : }
+
+
+
setRightPanelCustomizationOpen((open) => !open)}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{props.t("instanceShell.rightPanel.customize.tabs")}
+
+ {(tab) => {
+ const label = () => props.t(tab.labelKey)
+ const visible = () => !rightPanelCustomization().hiddenTabIds.includes(tab.id)
+ const disableHide = () => visible() && visibleRightPanelTabs().length <= 1
+ return (
+
+
+
+ )
+ }}
+
+
+
+
+
{props.t("instanceShell.rightPanel.customize.statusSections")}
+
+ {(section) => {
+ const label = () => props.t(section.labelKey)
+ const visible = () => !rightPanelCustomization().hiddenStatusSectionIds.includes(section.id)
+ const disableHide = () => visible() && visibleStatusSections().length <= 1
+ return (
+
+
+
+ )
+ }}
+
+
+
+
{props.t("instanceShell.rightPanel.customize.dragToReorder")}
+
+
+
+
+
+ {(tab) => }>{tab.render()}}
diff --git a/packages/ui/src/components/instance/shell/right-panel/registry.test.ts b/packages/ui/src/components/instance/shell/right-panel/registry.test.ts
new file mode 100644
index 000000000..a7b39d015
--- /dev/null
+++ b/packages/ui/src/components/instance/shell/right-panel/registry.test.ts
@@ -0,0 +1,71 @@
+import assert from "node:assert/strict"
+import { describe, it } from "node:test"
+
+import {
+ applyRightPanelItemCustomization,
+ collectRightPanelItems,
+ moveRightPanelItem,
+ parseRightPanelCustomization,
+ setRightPanelItemHidden,
+ type RightPanelItem,
+ type RightPanelTabModule,
+} from "./registry"
+
+const item = (id: string, order: number, alwaysVisible = false): RightPanelItem => ({ id, labelKey: id, order, alwaysVisible })
+const tab = (id: string, order: number): RightPanelTabModule => ({ ...item(id, order), render: () => undefined as any })
+
+describe("right panel registry", () => {
+ it("collects and orders module items", () => {
+ const items = collectRightPanelItems(
+ [
+ { id: "core", tabs: [tab("status", 40), tab("changes", 10)] },
+ { id: "plugin", tabs: [tab("custom", 30)] },
+ ],
+ "tabs",
+ )
+
+ assert.deepEqual(items.map((entry) => entry.id), ["changes", "custom", "status"])
+ })
+
+ it("rejects duplicate item ids", () => {
+ assert.throws(() => collectRightPanelItems([{ id: "core", tabs: [tab("status", 10), tab("status", 20)] }], "tabs"))
+ })
+
+ it("applies visibility and user order", () => {
+ const visible = applyRightPanelItemCustomization(
+ [item("changes", 10), item("files", 20), item("status", 30)],
+ ["status", "changes"],
+ ["files"],
+ )
+
+ assert.deepEqual(visible.map((entry) => entry.id), ["status", "changes"])
+ })
+
+ it("keeps always-visible items visible", () => {
+ const visible = applyRightPanelItemCustomization([item("configure", 100, true)], [], ["configure"])
+
+ assert.deepEqual(visible.map((entry) => entry.id), ["configure"])
+ })
+
+ it("moves items in normalized order", () => {
+ assert.deepEqual(moveRightPanelItem(["changes", "status", "files"], ["changes", "files", "status"], "status", -1), [
+ "status",
+ "changes",
+ "files",
+ ])
+ })
+
+ it("parses invalid customization safely", () => {
+ assert.deepEqual(parseRightPanelCustomization("{"), {
+ tabOrder: [],
+ hiddenTabIds: [],
+ statusSectionOrder: [],
+ hiddenStatusSectionIds: [],
+ })
+ })
+
+ it("toggles hidden ids", () => {
+ assert.deepEqual(setRightPanelItemHidden(["files"], "status", true), ["files", "status"])
+ assert.deepEqual(setRightPanelItemHidden(["files"], "files", false), [])
+ })
+})
diff --git a/packages/ui/src/components/instance/shell/right-panel/registry.ts b/packages/ui/src/components/instance/shell/right-panel/registry.ts
new file mode 100644
index 000000000..c24077eac
--- /dev/null
+++ b/packages/ui/src/components/instance/shell/right-panel/registry.ts
@@ -0,0 +1,113 @@
+import type { JSX } from "solid-js"
+
+export interface RightPanelItem {
+ id: string
+ labelKey: string
+ order: number
+ alwaysVisible?: boolean
+}
+
+export interface RightPanelTabModule extends RightPanelItem {
+ render: () => JSX.Element
+}
+
+export interface RightPanelSectionModule extends RightPanelItem {
+ tooltipKey: string
+ defaultExpanded?: boolean
+ render: () => JSX.Element
+}
+
+export interface RightPanelModule {
+ id: string
+ tabs?: readonly RightPanelTabModule[]
+ statusSections?: readonly RightPanelSectionModule[]
+}
+
+export interface RightPanelCustomization {
+ tabOrder: string[]
+ hiddenTabIds: string[]
+ statusSectionOrder: string[]
+ hiddenStatusSectionIds: string[]
+}
+
+export const DEFAULT_RIGHT_PANEL_CUSTOMIZATION: RightPanelCustomization = {
+ tabOrder: [],
+ hiddenTabIds: [],
+ statusSectionOrder: [],
+ hiddenStatusSectionIds: [],
+}
+
+export function parseRightPanelCustomization(value: string | null): RightPanelCustomization {
+ if (!value) return { ...DEFAULT_RIGHT_PANEL_CUSTOMIZATION }
+ try {
+ const parsed = JSON.parse(value) as Partial
+ return {
+ tabOrder: readStringArray(parsed.tabOrder),
+ hiddenTabIds: readStringArray(parsed.hiddenTabIds),
+ statusSectionOrder: readStringArray(parsed.statusSectionOrder),
+ hiddenStatusSectionIds: readStringArray(parsed.hiddenStatusSectionIds),
+ }
+ } catch {
+ return { ...DEFAULT_RIGHT_PANEL_CUSTOMIZATION }
+ }
+}
+
+export function collectRightPanelItems(modules: readonly RightPanelModule[], key: "tabs" | "statusSections"): T[] {
+ const items = modules.flatMap((module) => [...((module[key] ?? []) as unknown as readonly T[])])
+ const seen = new Set()
+ for (const item of items) {
+ if (!item.id) throw new Error(`Right panel ${key} must define an id`)
+ if (seen.has(item.id)) throw new Error(`Duplicate right panel ${key} id: ${item.id}`)
+ seen.add(item.id)
+ }
+ return sortRightPanelItems(items, [])
+}
+
+export function applyRightPanelItemCustomization(
+ items: readonly T[],
+ orderedIds: readonly string[],
+ hiddenIds: readonly string[],
+): T[] {
+ const hidden = new Set(hiddenIds)
+ const visible = items.filter((item) => item.alwaysVisible || !hidden.has(item.id))
+ return sortRightPanelItems(visible.length > 0 ? visible : items.slice(0, 1), orderedIds)
+}
+
+export function moveRightPanelItem(orderedIds: readonly string[], allIds: readonly string[], id: string, direction: -1 | 1): string[] {
+ const order = normalizeOrder(orderedIds, allIds)
+ const index = order.indexOf(id)
+ const nextIndex = index + direction
+ if (index === -1 || nextIndex < 0 || nextIndex >= order.length) return order
+ const next = [...order]
+ ;[next[index], next[nextIndex]] = [next[nextIndex], next[index]]
+ return next
+}
+
+export function setRightPanelItemHidden(hiddenIds: readonly string[], id: string, hidden: boolean): string[] {
+ const next = new Set(hiddenIds)
+ if (hidden) next.add(id)
+ else next.delete(id)
+ return [...next]
+}
+
+function sortRightPanelItems(items: readonly T[], orderedIds: readonly string[]): T[] {
+ const rank = new Map(orderedIds.map((id, index) => [id, index]))
+ return [...items].sort((left, right) => {
+ const leftRank = rank.get(left.id)
+ const rightRank = rank.get(right.id)
+ if (leftRank !== undefined || rightRank !== undefined) {
+ return (leftRank ?? Number.MAX_SAFE_INTEGER) - (rightRank ?? Number.MAX_SAFE_INTEGER)
+ }
+ return left.order - right.order || left.id.localeCompare(right.id)
+ })
+}
+
+function normalizeOrder(orderedIds: readonly string[], allIds: readonly string[]): string[] {
+ const known = new Set(allIds)
+ const ordered = orderedIds.filter((id, index) => known.has(id) && orderedIds.indexOf(id) === index)
+ return [...ordered, ...allIds.filter((id) => !ordered.includes(id))]
+}
+
+function readStringArray(value: unknown): string[] {
+ return Array.isArray(value) ? value.filter((entry): entry is string => typeof entry === "string") : []
+}
diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx b/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx
index 8bdc246a7..84baefd15 100644
--- a/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx
+++ b/packages/ui/src/components/instance/shell/right-panel/tabs/StatusTab.tsx
@@ -1,10 +1,18 @@
-import { For, Show, type Accessor, type Component } from "solid-js"
+import { For, Show, createMemo, type Accessor, type Component } from "solid-js"
import type { ToolState } from "@opencode-ai/sdk/v2"
+import {
+ DragDropProvider,
+ DragDropSensors,
+ SortableProvider,
+ closestCenter,
+ createSortable,
+ type DragEvent as SolidDndDragEvent,
+} from "@thisbeyond/solid-dnd"
import { Accordion } from "@kobalte/core"
import { Tooltip } from "@kobalte/core/tooltip"
import Switch from "@suid/material/Switch"
-import { BellRing, ChevronDown, Info, TerminalSquare, Trash2, XOctagon } from "lucide-solid"
+import { BellRing, ChevronDown, GripVertical, Info, TerminalSquare, Trash2, XOctagon } from "lucide-solid"
import type { Instance } from "../../../../../types/instance"
import type { BackgroundProcess } from "../../../../../../../server/src/api-types"
@@ -16,6 +24,7 @@ import { TodoListView } from "../../../../tool-call/renderers/todo"
import InstanceServiceStatus from "../../../../instance-service-status"
import { togglePermissionAutoAcceptForSession } from "../../../../../stores/instances"
import { isPermissionAutoAcceptEnabled } from "../../../../../stores/permission-auto-accept"
+import { applyRightPanelItemCustomization, type RightPanelCustomization, type RightPanelSectionModule } from "../registry"
interface StatusTabProps {
t: (key: string, vars?: Record) => string
@@ -35,7 +44,43 @@ interface StatusTabProps {
expandedItems: Accessor
onExpandedItemsChange: (values: string[]) => void
+ customization: Accessor
+ onCustomizationChange: (updater: (current: RightPanelCustomization) => RightPanelCustomization) => void
+ extraSections?: readonly RightPanelSectionModule[]
+}
+
+interface SortableStatusSectionProps {
+ section: RightPanelSectionModule
+ expanded: boolean
+ t: (key: string, vars?: Record) => string
+}
+const SortableStatusSection: Component = (props) => {
+ const sortable = createSortable(props.section.id)
+ return (
+
+
+
+ {props.section.render()}
+
+
+ )
}
const StatusTab: Component = (props) => {
@@ -182,71 +227,83 @@ const StatusTab: Component = (props) => {
)
}
- const statusSections = [
- {
- id: "yolo-mode",
- labelKey: "instanceShell.rightPanel.sections.yoloMode",
- tooltipKey: "instanceShell.rightPanel.sections.yoloMode.tooltip",
- render: renderYoloModeSection,
- },
- {
- id: "provider-usage",
- labelKey: "providerUsage.title",
- tooltipKey: "providerUsage.tooltip",
- render: renderProviderUsage,
- },
- {
- id: "plan",
- labelKey: "instanceShell.rightPanel.sections.plan",
- tooltipKey: "instanceShell.rightPanel.sections.plan.tooltip",
- render: renderPlanSectionContent,
- },
- {
- id: "background-processes",
- labelKey: "instanceShell.rightPanel.sections.backgroundProcesses",
- tooltipKey: "instanceShell.rightPanel.sections.backgroundProcesses.tooltip",
- render: renderBackgroundProcesses,
- },
- {
- id: "mcp",
- labelKey: "instanceShell.rightPanel.sections.mcp",
- tooltipKey: "instanceShell.rightPanel.sections.mcp.tooltip",
- render: () => (
-
- ),
- },
- {
- id: "lsp",
- labelKey: "instanceShell.rightPanel.sections.lsp",
- tooltipKey: "instanceShell.rightPanel.sections.lsp.tooltip",
- render: () => (
-
- ),
- },
- {
- id: "plugins",
- labelKey: "instanceShell.rightPanel.sections.plugins",
- tooltipKey: "instanceShell.rightPanel.sections.plugins.tooltip",
- render: () => (
-
- ),
- },
- ]
+ const statusSections = createMemo(() => {
+ const sections: RightPanelSectionModule[] = [
+ {
+ id: "yolo-mode",
+ labelKey: "instanceShell.rightPanel.sections.yoloMode",
+ tooltipKey: "instanceShell.rightPanel.sections.yoloMode.tooltip",
+ order: 10,
+ render: renderYoloModeSection,
+ },
+ {
+ id: "provider-usage",
+ labelKey: "providerUsage.title",
+ tooltipKey: "providerUsage.tooltip",
+ order: 20,
+ render: renderProviderUsage,
+ },
+ {
+ id: "plan",
+ labelKey: "instanceShell.rightPanel.sections.plan",
+ tooltipKey: "instanceShell.rightPanel.sections.plan.tooltip",
+ order: 30,
+ render: renderPlanSectionContent,
+ },
+ {
+ id: "background-processes",
+ labelKey: "instanceShell.rightPanel.sections.backgroundProcesses",
+ tooltipKey: "instanceShell.rightPanel.sections.backgroundProcesses.tooltip",
+ order: 40,
+ render: renderBackgroundProcesses,
+ },
+ {
+ id: "mcp",
+ labelKey: "instanceShell.rightPanel.sections.mcp",
+ tooltipKey: "instanceShell.rightPanel.sections.mcp.tooltip",
+ order: 50,
+ render: () => ,
+ },
+ {
+ id: "lsp",
+ labelKey: "instanceShell.rightPanel.sections.lsp",
+ tooltipKey: "instanceShell.rightPanel.sections.lsp.tooltip",
+ order: 60,
+ render: () => ,
+ },
+ {
+ id: "plugins",
+ labelKey: "instanceShell.rightPanel.sections.plugins",
+ tooltipKey: "instanceShell.rightPanel.sections.plugins.tooltip",
+ order: 70,
+ render: () => (
+
+ ),
+ },
+ ]
+ return applyRightPanelItemCustomization(
+ [...sections, ...(props.extraSections ?? [])],
+ props.customization().statusSectionOrder,
+ props.customization().hiddenStatusSectionIds,
+ )
+ })
+
+ const moveSection = (sourceId: string, targetId: string) => {
+ if (!sourceId || sourceId === targetId) return
+ const ids = statusSections().map((section) => section.id)
+ const sourceIndex = ids.indexOf(sourceId)
+ const targetIndex = ids.indexOf(targetId)
+ if (sourceIndex === -1 || targetIndex === -1) return
+ const next = [...ids]
+ const [moved] = next.splice(sourceIndex, 1)
+ next.splice(targetIndex, 0, moved)
+ props.onCustomizationChange((current) => ({ ...current, statusSectionOrder: next }))
+ }
+
+ const handleSectionDragEnd = ({ draggable, droppable }: SolidDndDragEvent) => {
+ if (!droppable) return
+ moveSection(String(draggable.id), String(droppable.id))
+ }
return (
@@ -263,31 +320,15 @@ const StatusTab: Component
= (props) => {
value={props.expandedItems()}
onChange={props.onExpandedItemsChange}
>
-
- {(section) => (
-
-
- {section.render()}
-
- )}
-
+
+
+ section.id)}>
+
+ {(section) => }
+
+
+
+
)
diff --git a/packages/ui/src/components/instance/shell/right-panel/tabs/status-sections.ts b/packages/ui/src/components/instance/shell/right-panel/tabs/status-sections.ts
new file mode 100644
index 000000000..87933d77f
--- /dev/null
+++ b/packages/ui/src/components/instance/shell/right-panel/tabs/status-sections.ts
@@ -0,0 +1,46 @@
+import type { RightPanelItem } from "../registry"
+
+export const CORE_STATUS_SECTION_ITEMS: readonly (RightPanelItem & { tooltipKey: string; defaultExpanded?: boolean })[] = [
+ {
+ id: "yolo-mode",
+ labelKey: "instanceShell.rightPanel.sections.yoloMode",
+ tooltipKey: "instanceShell.rightPanel.sections.yoloMode.tooltip",
+ order: 10,
+ },
+ {
+ id: "provider-usage",
+ labelKey: "providerUsage.title",
+ tooltipKey: "providerUsage.tooltip",
+ order: 20,
+ },
+ {
+ id: "plan",
+ labelKey: "instanceShell.rightPanel.sections.plan",
+ tooltipKey: "instanceShell.rightPanel.sections.plan.tooltip",
+ order: 30,
+ },
+ {
+ id: "background-processes",
+ labelKey: "instanceShell.rightPanel.sections.backgroundProcesses",
+ tooltipKey: "instanceShell.rightPanel.sections.backgroundProcesses.tooltip",
+ order: 40,
+ },
+ {
+ id: "mcp",
+ labelKey: "instanceShell.rightPanel.sections.mcp",
+ tooltipKey: "instanceShell.rightPanel.sections.mcp.tooltip",
+ order: 50,
+ },
+ {
+ id: "lsp",
+ labelKey: "instanceShell.rightPanel.sections.lsp",
+ tooltipKey: "instanceShell.rightPanel.sections.lsp.tooltip",
+ order: 60,
+ },
+ {
+ id: "plugins",
+ labelKey: "instanceShell.rightPanel.sections.plugins",
+ tooltipKey: "instanceShell.rightPanel.sections.plugins.tooltip",
+ order: 70,
+ },
+]
diff --git a/packages/ui/src/components/instance/shell/right-panel/types.ts b/packages/ui/src/components/instance/shell/right-panel/types.ts
index aa407b6e0..e24e3b46a 100644
--- a/packages/ui/src/components/instance/shell/right-panel/types.ts
+++ b/packages/ui/src/components/instance/shell/right-panel/types.ts
@@ -1,4 +1,4 @@
-export type RightPanelTab = "git-changes" | "files" | "status"
+export type RightPanelTab = string
export type DiffViewMode = "split" | "unified"
@@ -27,7 +27,7 @@ export type GitChangeSection = "staged" | "unstaged"
export interface GitChangeListItem {
id: string
path: string
- originalPath?: string | null
+ originalPath?: string | null
section: GitChangeSection
status: GitChangeStatus
additions: number
diff --git a/packages/ui/src/components/instance/shell/storage.ts b/packages/ui/src/components/instance/shell/storage.ts
index 42bf0deed..0e685d4e9 100644
--- a/packages/ui/src/components/instance/shell/storage.ts
+++ b/packages/ui/src/components/instance/shell/storage.ts
@@ -16,6 +16,7 @@ export const LEFT_OPEN_STORAGE_KEY = "opencode-session-left-drawer-open-v1"
export const RIGHT_OPEN_STORAGE_KEY = "opencode-session-right-drawer-open-v1"
export const RIGHT_PANEL_TAB_STORAGE_KEY = "opencode-session-right-panel-tab-v2"
export const LEGACY_RIGHT_PANEL_TAB_STORAGE_KEY = "opencode-session-right-panel-tab-v1"
+export const RIGHT_PANEL_CUSTOMIZATION_STORAGE_KEY = "opencode-session-right-panel-customization-v1"
export const RIGHT_PANEL_FILES_SPLIT_WIDTH_KEY = "opencode-session-right-panel-files-split-width-v1"
export const RIGHT_PANEL_GIT_CHANGES_SPLIT_WIDTH_KEY = "opencode-session-right-panel-git-changes-split-width-v1"
export const RIGHT_PANEL_FILES_LIST_OPEN_NONPHONE_KEY = "opencode-session-right-panel-files-list-open-nonphone-v1"
@@ -65,14 +66,13 @@ export function persistOpenState(side: "left" | "right", value: boolean) {
writeClientLayoutValue(getOpenStorageKey(side), value ? "true" : "false")
}
-export function readStoredRightPanelTab(
- defaultValue: "git-changes" | "files" | "status",
-): "git-changes" | "files" | "status" {
+export function readStoredRightPanelTab(defaultValue: string): string {
const stored = readClientLayoutValue(RIGHT_PANEL_TAB_STORAGE_KEY)
if (stored === "status") return "status"
if (stored === "changes") return "git-changes"
if (stored === "git-changes") return "git-changes"
if (stored === "files") return "files"
+ if (stored) return stored
// Migrate from v1 (where the stored values were the internal tab ids).
const legacy = readClientLayoutValue(LEGACY_RIGHT_PANEL_TAB_STORAGE_KEY)
diff --git a/packages/ui/src/lib/i18n/messages/de/instance.ts b/packages/ui/src/lib/i18n/messages/de/instance.ts
index 5e8be37ba..5b5e9018c 100644
--- a/packages/ui/src/lib/i18n/messages/de/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/de/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "Dateien",
"instanceShell.rightPanel.tabs.status": "Status",
"instanceShell.rightPanel.tabs.ariaLabel": "Tabs im rechten Panel",
+ "instanceShell.rightPanel.customize.toggle": "Rechtes Panel anpassen",
+ "instanceShell.rightPanel.customize.title": "Rechtes Panel anpassen",
+ "instanceShell.rightPanel.customize.description": "Tabs und Statusabschnitte anzeigen oder ausblenden.",
+ "instanceShell.rightPanel.customize.reset": "Zurücksetzen",
+ "instanceShell.rightPanel.customize.tabs": "Tabs",
+ "instanceShell.rightPanel.customize.statusSections": "Statusabschnitte",
+ "instanceShell.rightPanel.customize.dragToReorder": "Tabs oder Statusabschnitte ziehen, um sie neu anzuordnen.",
+ "instanceShell.rightPanel.customize.moveUp": "Nach oben",
+ "instanceShell.rightPanel.customize.moveDown": "Nach unten",
+ "instanceShell.rightPanel.customize.moveTabUp": "Tab {label} nach oben verschieben",
+ "instanceShell.rightPanel.customize.moveTabDown": "Tab {label} nach unten verschieben",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "Statusabschnitt {label} nach oben verschieben",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "Statusabschnitt {label} nach unten verschieben",
"instanceShell.rightPanel.actions.refresh": "Aktualisieren",
"instanceShell.rightPanel.actions.save": "Speichern (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "Möchten Sie die Änderungen an \"{path}\" speichern, bevor Sie wechseln?",
diff --git a/packages/ui/src/lib/i18n/messages/en/instance.ts b/packages/ui/src/lib/i18n/messages/en/instance.ts
index d5f6425b5..f3371780b 100644
--- a/packages/ui/src/lib/i18n/messages/en/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/en/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "Files",
"instanceShell.rightPanel.tabs.status": "Status",
"instanceShell.rightPanel.tabs.ariaLabel": "Right panel tabs",
+ "instanceShell.rightPanel.customize.toggle": "Customize right panel",
+ "instanceShell.rightPanel.customize.title": "Customize right panel",
+ "instanceShell.rightPanel.customize.description": "Show or hide tabs and status sections.",
+ "instanceShell.rightPanel.customize.reset": "Reset",
+ "instanceShell.rightPanel.customize.tabs": "Tabs",
+ "instanceShell.rightPanel.customize.statusSections": "Status sections",
+ "instanceShell.rightPanel.customize.dragToReorder": "Drag tabs or status sections to reorder.",
+ "instanceShell.rightPanel.customize.moveUp": "Up",
+ "instanceShell.rightPanel.customize.moveDown": "Down",
+ "instanceShell.rightPanel.customize.moveTabUp": "Move {label} tab up",
+ "instanceShell.rightPanel.customize.moveTabDown": "Move {label} tab down",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "Move {label} status section up",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "Move {label} status section down",
"instanceShell.rightPanel.actions.refresh": "Refresh",
"instanceShell.rightPanel.actions.save": "Save (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "Do you want to save changes to \"{path}\" before switching?",
diff --git a/packages/ui/src/lib/i18n/messages/es/instance.ts b/packages/ui/src/lib/i18n/messages/es/instance.ts
index 7b5627934..a8237d414 100644
--- a/packages/ui/src/lib/i18n/messages/es/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/es/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "Archivos",
"instanceShell.rightPanel.tabs.status": "Estado",
"instanceShell.rightPanel.tabs.ariaLabel": "Pestañas del panel derecho",
+ "instanceShell.rightPanel.customize.toggle": "Personalizar panel derecho",
+ "instanceShell.rightPanel.customize.title": "Personalizar panel derecho",
+ "instanceShell.rightPanel.customize.description": "Muestra u oculta pestañas y secciones de estado.",
+ "instanceShell.rightPanel.customize.reset": "Restablecer",
+ "instanceShell.rightPanel.customize.tabs": "Pestañas",
+ "instanceShell.rightPanel.customize.statusSections": "Secciones de estado",
+ "instanceShell.rightPanel.customize.dragToReorder": "Arrastra pestañas o secciones de estado para reordenarlas.",
+ "instanceShell.rightPanel.customize.moveUp": "Subir",
+ "instanceShell.rightPanel.customize.moveDown": "Bajar",
+ "instanceShell.rightPanel.customize.moveTabUp": "Subir pestaña {label}",
+ "instanceShell.rightPanel.customize.moveTabDown": "Bajar pestaña {label}",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "Subir sección de estado {label}",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "Bajar sección de estado {label}",
"instanceShell.rightPanel.actions.refresh": "Actualizar",
"instanceShell.rightPanel.actions.save": "Guardar (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "¿Deseas guardar los cambios en \"{path}\" antes de cambiar?",
diff --git a/packages/ui/src/lib/i18n/messages/fr/instance.ts b/packages/ui/src/lib/i18n/messages/fr/instance.ts
index d1ba7d3c9..87b84b634 100644
--- a/packages/ui/src/lib/i18n/messages/fr/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/fr/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "Fichiers",
"instanceShell.rightPanel.tabs.status": "Statut",
"instanceShell.rightPanel.tabs.ariaLabel": "Onglets du panneau droit",
+ "instanceShell.rightPanel.customize.toggle": "Personnaliser le panneau droit",
+ "instanceShell.rightPanel.customize.title": "Personnaliser le panneau droit",
+ "instanceShell.rightPanel.customize.description": "Afficher ou masquer les onglets et sections de statut.",
+ "instanceShell.rightPanel.customize.reset": "Réinitialiser",
+ "instanceShell.rightPanel.customize.tabs": "Onglets",
+ "instanceShell.rightPanel.customize.statusSections": "Sections de statut",
+ "instanceShell.rightPanel.customize.dragToReorder": "Faites glisser les onglets ou sections de statut pour les réordonner.",
+ "instanceShell.rightPanel.customize.moveUp": "Monter",
+ "instanceShell.rightPanel.customize.moveDown": "Descendre",
+ "instanceShell.rightPanel.customize.moveTabUp": "Monter l'onglet {label}",
+ "instanceShell.rightPanel.customize.moveTabDown": "Descendre l'onglet {label}",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "Monter la section de statut {label}",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "Descendre la section de statut {label}",
"instanceShell.rightPanel.actions.refresh": "Actualiser",
"instanceShell.rightPanel.actions.save": "Enregistrer (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "Voulez-vous enregistrer les modifications de \"{path}\" avant de changer ?",
diff --git a/packages/ui/src/lib/i18n/messages/he/instance.ts b/packages/ui/src/lib/i18n/messages/he/instance.ts
index 5735fca56..4d5a4af43 100644
--- a/packages/ui/src/lib/i18n/messages/he/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/he/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "קבצים",
"instanceShell.rightPanel.tabs.status": "סטטוס",
"instanceShell.rightPanel.tabs.ariaLabel": "לשוניות לוח ימני",
+ "instanceShell.rightPanel.customize.toggle": "התאמת הלוח הימני",
+ "instanceShell.rightPanel.customize.title": "התאמת הלוח הימני",
+ "instanceShell.rightPanel.customize.description": "הצגה או הסתרה של כרטיסיות ומקטעי סטטוס.",
+ "instanceShell.rightPanel.customize.reset": "איפוס",
+ "instanceShell.rightPanel.customize.tabs": "כרטיסיות",
+ "instanceShell.rightPanel.customize.statusSections": "מקטעי סטטוס",
+ "instanceShell.rightPanel.customize.dragToReorder": "גררו כרטיסיות או מקטעי סטטוס כדי לסדר מחדש.",
+ "instanceShell.rightPanel.customize.moveUp": "למעלה",
+ "instanceShell.rightPanel.customize.moveDown": "למטה",
+ "instanceShell.rightPanel.customize.moveTabUp": "העברת הכרטיסייה {label} למעלה",
+ "instanceShell.rightPanel.customize.moveTabDown": "העברת הכרטיסייה {label} למטה",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "העברת מקטע הסטטוס {label} למעלה",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "העברת מקטע הסטטוס {label} למטה",
"instanceShell.rightPanel.actions.refresh": "רענן",
"instanceShell.rightPanel.actions.save": "שמור (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "האם ברצונך לשמור את השינויים ב-\"{path}\" לפני המעבר?",
diff --git a/packages/ui/src/lib/i18n/messages/ja/instance.ts b/packages/ui/src/lib/i18n/messages/ja/instance.ts
index e2f3e18e1..b4eb22255 100644
--- a/packages/ui/src/lib/i18n/messages/ja/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/ja/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "ファイル",
"instanceShell.rightPanel.tabs.status": "ステータス",
"instanceShell.rightPanel.tabs.ariaLabel": "右パネルのタブ",
+ "instanceShell.rightPanel.customize.toggle": "右パネルをカスタマイズ",
+ "instanceShell.rightPanel.customize.title": "右パネルをカスタマイズ",
+ "instanceShell.rightPanel.customize.description": "タブとステータスセクションの表示/非表示を切り替えます。",
+ "instanceShell.rightPanel.customize.reset": "リセット",
+ "instanceShell.rightPanel.customize.tabs": "タブ",
+ "instanceShell.rightPanel.customize.statusSections": "ステータスセクション",
+ "instanceShell.rightPanel.customize.dragToReorder": "タブまたはステータスセクションをドラッグして並べ替えます。",
+ "instanceShell.rightPanel.customize.moveUp": "上へ",
+ "instanceShell.rightPanel.customize.moveDown": "下へ",
+ "instanceShell.rightPanel.customize.moveTabUp": "{label} タブを上へ移動",
+ "instanceShell.rightPanel.customize.moveTabDown": "{label} タブを下へ移動",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "{label} ステータスセクションを上へ移動",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "{label} ステータスセクションを下へ移動",
"instanceShell.rightPanel.actions.refresh": "更新",
"instanceShell.rightPanel.actions.save": "保存 (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "「{path}」への変更を切り替え前に保存しますか?",
diff --git a/packages/ui/src/lib/i18n/messages/ne/instance.ts b/packages/ui/src/lib/i18n/messages/ne/instance.ts
index 2de2157ef..7f5e938a4 100644
--- a/packages/ui/src/lib/i18n/messages/ne/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/ne/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "फाइलहरू",
"instanceShell.rightPanel.tabs.status": "स्थिति",
"instanceShell.rightPanel.tabs.ariaLabel": "दायाँ प्यानल ट्याबहरू",
+ "instanceShell.rightPanel.customize.toggle": "दायाँ प्यानल अनुकूलन गर्नुहोस्",
+ "instanceShell.rightPanel.customize.title": "दायाँ प्यानल अनुकूलन गर्नुहोस्",
+ "instanceShell.rightPanel.customize.description": "ट्याबहरू र स्थिति खण्डहरू देखाउनुहोस् वा लुकाउनुहोस्।",
+ "instanceShell.rightPanel.customize.reset": "रिसेट गर्नुहोस्",
+ "instanceShell.rightPanel.customize.tabs": "ट्याबहरू",
+ "instanceShell.rightPanel.customize.statusSections": "स्थिति खण्डहरू",
+ "instanceShell.rightPanel.customize.dragToReorder": "पुनःक्रमबद्ध गर्न ट्याब वा स्थिति खण्डहरू तान्नुहोस्।",
+ "instanceShell.rightPanel.customize.moveUp": "माथि",
+ "instanceShell.rightPanel.customize.moveDown": "तल",
+ "instanceShell.rightPanel.customize.moveTabUp": "{label} ट्याब माथि सार्नुहोस्",
+ "instanceShell.rightPanel.customize.moveTabDown": "{label} ट्याब तल सार्नुहोस्",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "{label} स्थिति खण्ड माथि सार्नुहोस्",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "{label} स्थिति खण्ड तल सार्नुहोस्",
"instanceShell.rightPanel.actions.refresh": "रिफ्रेस",
"instanceShell.rightPanel.actions.save": "बचत (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "परिवर्तन गर्नु अघि \"{path}\" मा गरिएका परिवर्तनहरू बचत गर्न चाहनुहुन्छ?",
diff --git a/packages/ui/src/lib/i18n/messages/ru/instance.ts b/packages/ui/src/lib/i18n/messages/ru/instance.ts
index a48d505b7..44386c157 100644
--- a/packages/ui/src/lib/i18n/messages/ru/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/ru/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "Файлы",
"instanceShell.rightPanel.tabs.status": "Статус",
"instanceShell.rightPanel.tabs.ariaLabel": "Вкладки правой панели",
+ "instanceShell.rightPanel.customize.toggle": "Настроить правую панель",
+ "instanceShell.rightPanel.customize.title": "Настроить правую панель",
+ "instanceShell.rightPanel.customize.description": "Показывайте или скрывайте вкладки и секции статуса.",
+ "instanceShell.rightPanel.customize.reset": "Сбросить",
+ "instanceShell.rightPanel.customize.tabs": "Вкладки",
+ "instanceShell.rightPanel.customize.statusSections": "Секции статуса",
+ "instanceShell.rightPanel.customize.dragToReorder": "Перетаскивайте вкладки или секции статуса, чтобы изменить порядок.",
+ "instanceShell.rightPanel.customize.moveUp": "Вверх",
+ "instanceShell.rightPanel.customize.moveDown": "Вниз",
+ "instanceShell.rightPanel.customize.moveTabUp": "Переместить вкладку {label} вверх",
+ "instanceShell.rightPanel.customize.moveTabDown": "Переместить вкладку {label} вниз",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "Переместить секцию статуса {label} вверх",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "Переместить секцию статуса {label} вниз",
"instanceShell.rightPanel.actions.refresh": "Обновить",
"instanceShell.rightPanel.actions.save": "Сохранить (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "Сохранить изменения в \"{path}\" перед переключением?",
diff --git a/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts b/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts
index 640a40a2c..f1bb74eb6 100644
--- a/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts
+++ b/packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts
@@ -93,6 +93,19 @@ export const instanceMessages = {
"instanceShell.rightPanel.tabs.files": "文件",
"instanceShell.rightPanel.tabs.status": "状态",
"instanceShell.rightPanel.tabs.ariaLabel": "右侧面板标签页",
+ "instanceShell.rightPanel.customize.toggle": "自定义右侧面板",
+ "instanceShell.rightPanel.customize.title": "自定义右侧面板",
+ "instanceShell.rightPanel.customize.description": "显示或隐藏标签页和状态分区。",
+ "instanceShell.rightPanel.customize.reset": "重置",
+ "instanceShell.rightPanel.customize.tabs": "标签页",
+ "instanceShell.rightPanel.customize.statusSections": "状态分区",
+ "instanceShell.rightPanel.customize.dragToReorder": "拖动标签页或状态分区以重新排序。",
+ "instanceShell.rightPanel.customize.moveUp": "上移",
+ "instanceShell.rightPanel.customize.moveDown": "下移",
+ "instanceShell.rightPanel.customize.moveTabUp": "上移 {label} 标签页",
+ "instanceShell.rightPanel.customize.moveTabDown": "下移 {label} 标签页",
+ "instanceShell.rightPanel.customize.moveStatusSectionUp": "上移 {label} 状态分区",
+ "instanceShell.rightPanel.customize.moveStatusSectionDown": "下移 {label} 状态分区",
"instanceShell.rightPanel.actions.refresh": "刷新",
"instanceShell.rightPanel.actions.save": "保存 (Ctrl+S)",
"instanceShell.rightPanel.actions.saveConfirm.message": "切换前是否保存对 \"{path}\" 的更改?",
diff --git a/packages/ui/src/styles/panels/right-panel.css b/packages/ui/src/styles/panels/right-panel.css
index 14bd0d36e..1d0bb460c 100644
--- a/packages/ui/src/styles/panels/right-panel.css
+++ b/packages/ui/src/styles/panels/right-panel.css
@@ -38,6 +38,7 @@
.right-panel-tab {
@apply inline-flex items-center gap-2 px-4 py-2 text-xs font-medium transition-all duration-150 relative;
font-family: var(--font-family-sans);
+ cursor: grab;
outline: none;
border: 1px solid transparent;
border-bottom: none;
@@ -46,6 +47,14 @@
z-index: 1;
}
+.right-panel-tab-draggable {
+ display: inline-flex;
+}
+
+.right-panel-tab-draggable-active {
+ opacity: 0.75;
+}
+
.right-panel-tab:focus-visible {
@apply ring-2 ring-offset-1;
ring-color: var(--accent-primary);
@@ -768,6 +777,99 @@
min-height: 0;
}
+.right-panel-section-draggable-active {
+ opacity: 0.75;
+}
+
+.right-panel-customization-popover {
+ position: absolute;
+ top: 2.75rem;
+ inset-inline-start: 0.5rem;
+ z-index: 20;
+ width: min(18rem, calc(100% - 1rem));
+ border: 1px solid var(--border-base);
+ background-color: var(--surface-secondary);
+ box-shadow: var(--popover-shadow);
+ padding: 0.75rem;
+}
+
+.right-panel-customization-header {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 0.75rem;
+ margin-bottom: 0.75rem;
+}
+
+.right-panel-customization-grid {
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 0.5rem;
+}
+
+.right-panel-customization-group {
+ min-width: 0;
+ border: 1px solid var(--border-base);
+ background-color: var(--surface-primary);
+ padding: 0.5rem;
+}
+
+.right-panel-customization-group-title {
+ color: var(--text-secondary);
+ font-size: 0.75rem;
+ font-weight: 600;
+ margin-bottom: 0.35rem;
+}
+
+.right-panel-customization-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 0.5rem;
+ padding: 0.25rem 0;
+}
+
+.right-panel-customization-label {
+ min-width: 0;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--text-primary);
+ font-size: 0.75rem;
+}
+
+.right-panel-customization-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 1px solid var(--border-base);
+ background-color: transparent;
+ color: var(--text-secondary);
+ font-size: 0.75rem;
+ font-weight: 500;
+ line-height: 1rem;
+ padding: 0.25rem 0.5rem;
+ transition: color 150ms, background-color 150ms;
+}
+
+.right-panel-customization-button:hover:not(:disabled) {
+ color: var(--text-primary);
+ background-color: var(--surface-hover);
+}
+
+.right-panel-customization-button:focus-visible {
+ outline: none;
+ box-shadow: 0 0 0 2px var(--focus-ring-offset), 0 0 0 4px var(--focus-ring-color);
+}
+
+.right-panel-customization-hint {
+ margin-top: 0.5rem;
+ color: var(--text-tertiary);
+ font-size: 0.6875rem;
+ line-height: 1rem;
+}
+
/* Section info tooltip */
.section-info-trigger {
@apply inline-flex items-center justify-center p-0.5 rounded transition-all duration-150;