diff --git a/packages/app/src/components/server/server-row-menu.tsx b/packages/app/src/components/server/server-row-menu.tsx index ece38ab49ba2..9d5f5e5a32d4 100644 --- a/packages/app/src/components/server/server-row-menu.tsx +++ b/packages/app/src/components/server/server-row-menu.tsx @@ -19,7 +19,7 @@ export const ServerRowMenu: Component<{ const isDefault = () => props.controller.defaultKey() === key return ( - + navigate(newSessionHref()) + const newTabKeybind = "mod+t" command.register("tabs", () => { const current = currentTab() @@ -341,7 +345,7 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { id: "tab.new", category: "tab", title: language.t("command.session.new"), - keybind: "mod+t", + keybind: newTabKeybind, hidden: true, onSelect: openNewTab, }, @@ -540,16 +544,26 @@ export function Titlebar(props: { update?: TitlebarUpdate }) { /> - } - as="a" - href={newSessionHref()} - aria-label={language.t("command.session.new")} - /> + + {language.t("command.session.new")} + + + } + > + } + as="a" + href={newSessionHref()} + aria-label={language.t("command.session.new")} + /> +
@@ -813,7 +827,8 @@ function TabNavItem(props: { return (
{ if (event.button !== 1) return @@ -848,10 +863,12 @@ function TabNavItem(props: {
{ if (event.button !== 1) return closeTab(event) @@ -952,7 +970,8 @@ function NewSessionTabItem(props: { ref?: HTMLDivElement; href: string; title: s return (
{ if (event.button !== 1) return closeTab(event) diff --git a/packages/app/src/context/command.tsx b/packages/app/src/context/command.tsx index e979ad6a0595..2a6e61b3a033 100644 --- a/packages/app/src/context/command.tsx +++ b/packages/app/src/context/command.tsx @@ -170,11 +170,11 @@ export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean return false } -export function formatKeybind(config: string, t?: (key: KeyLabel) => string): string { - if (!config || config === "none") return "" +function formatKeybindParts(config: string, t?: (key: KeyLabel) => string): string[] { + if (!config || config === "none") return [] const keybinds = parseKeybind(config) - if (keybinds.length === 0) return "" + if (keybinds.length === 0) return [] const kb = keybinds[0] const parts: string[] = [] @@ -184,43 +184,54 @@ export function formatKeybind(config: string, t?: (key: KeyLabel) => string): st if (kb.shift) parts.push(IS_MAC ? "⇧" : keyText("common.key.shift", t)) if (kb.meta) parts.push(IS_MAC ? "⌘" : keyText("common.key.meta", t)) - if (kb.key) { - const keys: Record = { - arrowup: "↑", - arrowdown: "↓", - arrowleft: "←", - arrowright: "→", - comma: ",", - plus: "+", - } - const named: Record = { - backspace: "common.key.backspace", - delete: "common.key.delete", - end: "common.key.end", - enter: "common.key.enter", - esc: "common.key.esc", - escape: "common.key.esc", - home: "common.key.home", - insert: "common.key.insert", - pagedown: "common.key.pageDown", - pageup: "common.key.pageUp", - space: "common.key.space", - tab: "common.key.tab", - } - const key = kb.key.toLowerCase() - const displayKey = - keys[key] ?? + if (!kb.key) return parts + + const keys: Record = { + arrowup: "↑", + arrowdown: "↓", + arrowleft: "←", + arrowright: "→", + comma: ",", + plus: "+", + } + const named: Record = { + backspace: "common.key.backspace", + delete: "common.key.delete", + end: "common.key.end", + enter: "common.key.enter", + esc: "common.key.esc", + escape: "common.key.esc", + home: "common.key.home", + insert: "common.key.insert", + pagedown: "common.key.pageDown", + pageup: "common.key.pageUp", + space: "common.key.space", + tab: "common.key.tab", + } + const key = kb.key.toLowerCase() + parts.push( + keys[key] ?? (named[key] ? keyText(named[key], t) : key.length === 1 ? key.toUpperCase() - : key.charAt(0).toUpperCase() + key.slice(1)) - parts.push(displayKey) - } + : key.charAt(0).toUpperCase() + key.slice(1)), + ) + + return parts +} +export function formatKeybind(config: string, t?: (key: KeyLabel) => string): string { + const parts = formatKeybindParts(config, t) + if (parts.length === 0) return "" return IS_MAC ? parts.join("") : parts.join("+") } +// KeybindV2 takes an array instead of a string +export function formatKeybindKeys(config: string, t?: (key: KeyLabel) => string): string[] { + return formatKeybindParts(config, t) +} + function isEditableTarget(target: EventTarget | null) { if (!(target instanceof HTMLElement)) return false if (target.isContentEditable) return true diff --git a/packages/app/src/pages/home.tsx b/packages/app/src/pages/home.tsx index 24c7e4c3abe4..5ee4c2911f98 100644 --- a/packages/app/src/pages/home.tsx +++ b/packages/app/src/pages/home.tsx @@ -12,6 +12,7 @@ import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2" +import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { getProjectAvatarVariant, useLayout, type LocalProject } from "@/context/layout" import { useNavigate } from "@solidjs/router" import { base64Encode } from "@opencode-ai/core/util/encode" @@ -71,7 +72,7 @@ type HomeSessionGroup = { const HOME_SESSION_SEARCH_RESULTS_ID = "home-session-search-results" const HOME_SEARCH_RESULT_ROW = - "flex h-10 w-full shrink-0 cursor-default items-center gap-2 border-0 py-3 pl-4 pr-6 text-left transition-[background-color] duration-[120ms] ease-in-out hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none" + "flex h-10 w-full shrink-0 cursor-default items-center gap-2 border-0 py-3 pl-[18px] pr-6 text-left transition-[background-color] duration-[120ms] ease-in-out hover:bg-v2-overlay-simple-overlay-hover focus-visible:bg-v2-overlay-simple-overlay-hover focus-visible:outline-none" const HOME_SEARCH_RESULT_TITLE = "min-w-0 overflow-hidden text-ellipsis whitespace-nowrap text-[13px] leading-4 tracking-[-0.04px] text-v2-text-text-base [font-weight:530]" const HOME_SEARCH_RESULT_META = @@ -374,6 +375,7 @@ function HomeDesign() { open={searchOpen()} loading={sessionLoad.isLoading} results={searchResults()} + showProjectName={!selectedProject()} server={state.selection.server} activeServer={state.selection.server === server.key} noResultsLabel={language.t("home.sessions.search.noResults", { query: search() })} @@ -414,6 +416,7 @@ function HomeDesign() { {(record) => ( - } - onClick={() => props.chooseProject(global.servers.list()[0]!)} - aria-label={props.language.t("home.project.add")} - /> + + } + onClick={() => props.chooseProject(global.servers.list()[0]!)} + aria-label={props.language.t("home.project.add")} + /> +
setState("menuOpen", open)} /> - } - aria-label={props.language.t("home.project.add")} - onClick={() => props.chooseProject(props.server)} - /> + + } + aria-label={props.language.t("home.project.add")} + onClick={() => props.chooseProject(props.server)} + /> +
) @@ -644,19 +655,11 @@ function HomeProjectRow(props: { {displayName(props.project)}
- } - aria-label={props.language.t("command.session.new")} - onClick={() => props.openNewSession(props.server, props.project.worktree)} - /> props.editProject(props.server, props.project)}> - {props.language.t("common.edit")} + {props.language.t("dialog.project.edit.title")} + } + aria-label={props.language.t("command.session.new")} + onClick={() => props.openNewSession(props.server, props.project.worktree)} + />
) @@ -736,7 +747,7 @@ function HomeSessionLeading(props: {