From c8960a7b193f212721b25c22346730a6e90876c9 Mon Sep 17 00:00:00 2001 From: Kit Langton Date: Wed, 21 Oct 2026 20:33:49 -0700 Subject: [PATCH] feat(app): open session Extensions lists on hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit One flyout beside the Extensions card glides between MCP, Plugins, Skills and LSP and clips to each list. Lists swap at once while the box glides, so two lists never show together. Rows switch at once; a safe triangle, measured when a list opens and on scroll or resize, keeps a diagonal move into the open list from switching rows. ArrowRight and Enter open into the list, ArrowLeft and Escape close back to the row. The rows are disclosures (aria-expanded, aria-controls) of labelled groups. The dot is the status: a failed dot shows its error on hover and opens the details, status words stay for assistive technology, and the "Configured …" headings are gone. --- .../regression/mcp-workspace-toggle.spec.ts | 26 +- .../mobile-extension-drawers.spec.ts | 2 +- .../e2e/regression/session-summary.spec.ts | 124 ++- .../gui-extensions/src/details/details.css | 248 +++++- .../gui-extensions/src/details/i18n/am.ts | 4 - .../gui-extensions/src/details/i18n/ar.ts | 4 - .../gui-extensions/src/details/i18n/az.ts | 4 - .../gui-extensions/src/details/i18n/bg.ts | 4 - .../gui-extensions/src/details/i18n/bn.ts | 4 - .../gui-extensions/src/details/i18n/br.ts | 4 - .../gui-extensions/src/details/i18n/bs.ts | 4 - .../gui-extensions/src/details/i18n/ca.ts | 4 - .../gui-extensions/src/details/i18n/cs.ts | 4 - .../gui-extensions/src/details/i18n/da.ts | 4 - .../gui-extensions/src/details/i18n/de.ts | 4 - .../gui-extensions/src/details/i18n/dv.ts | 4 - .../gui-extensions/src/details/i18n/dz.ts | 4 - .../gui-extensions/src/details/i18n/el.ts | 4 - .../gui-extensions/src/details/i18n/en.ts | 4 - .../gui-extensions/src/details/i18n/es.ts | 4 - .../gui-extensions/src/details/i18n/et.ts | 4 - .../gui-extensions/src/details/i18n/fa.ts | 4 - .../gui-extensions/src/details/i18n/fi.ts | 4 - .../gui-extensions/src/details/i18n/fo.ts | 4 - .../gui-extensions/src/details/i18n/fr.ts | 4 - .../gui-extensions/src/details/i18n/he.ts | 4 - .../gui-extensions/src/details/i18n/hi.ts | 4 - .../gui-extensions/src/details/i18n/hr.ts | 4 - .../gui-extensions/src/details/i18n/hu.ts | 4 - .../gui-extensions/src/details/i18n/hy.ts | 4 - .../gui-extensions/src/details/i18n/id.ts | 4 - .../gui-extensions/src/details/i18n/is.ts | 4 - .../gui-extensions/src/details/i18n/it.ts | 4 - .../gui-extensions/src/details/i18n/ja.ts | 4 - .../gui-extensions/src/details/i18n/ka.ts | 4 - .../gui-extensions/src/details/i18n/km.ts | 4 - .../gui-extensions/src/details/i18n/ko.ts | 4 - .../gui-extensions/src/details/i18n/lo.ts | 4 - .../gui-extensions/src/details/i18n/lt.ts | 4 - .../gui-extensions/src/details/i18n/lv.ts | 4 - .../gui-extensions/src/details/i18n/mk.ts | 4 - .../gui-extensions/src/details/i18n/mn.ts | 4 - .../gui-extensions/src/details/i18n/ms.ts | 4 - .../gui-extensions/src/details/i18n/my.ts | 4 - .../gui-extensions/src/details/i18n/ne.ts | 4 - .../gui-extensions/src/details/i18n/nl.ts | 4 - .../gui-extensions/src/details/i18n/no.ts | 4 - .../gui-extensions/src/details/i18n/pa.ts | 4 - .../gui-extensions/src/details/i18n/pl.ts | 4 - .../gui-extensions/src/details/i18n/ro.ts | 4 - .../gui-extensions/src/details/i18n/ru.ts | 4 - .../gui-extensions/src/details/i18n/si.ts | 4 - .../gui-extensions/src/details/i18n/sk.ts | 4 - .../gui-extensions/src/details/i18n/sl.ts | 4 - .../gui-extensions/src/details/i18n/sq.ts | 4 - .../gui-extensions/src/details/i18n/sr.ts | 4 - .../gui-extensions/src/details/i18n/sv.ts | 4 - .../gui-extensions/src/details/i18n/tg.ts | 4 - .../gui-extensions/src/details/i18n/th.ts | 4 - .../gui-extensions/src/details/i18n/tk.ts | 4 - .../gui-extensions/src/details/i18n/tr.ts | 4 - .../gui-extensions/src/details/i18n/uk.ts | 4 - .../gui-extensions/src/details/i18n/ur.ts | 4 - .../gui-extensions/src/details/i18n/uz.ts | 4 - .../gui-extensions/src/details/i18n/vi.ts | 4 - .../gui-extensions/src/details/i18n/zh.ts | 4 - .../gui-extensions/src/details/i18n/zht.ts | 4 - .../gui-extensions/src/details/popover.tsx | 32 +- .../src/details/server-panel.tsx | 819 +++++++++++++----- 69 files changed, 934 insertions(+), 569 deletions(-) diff --git a/packages/app/e2e/regression/mcp-workspace-toggle.spec.ts b/packages/app/e2e/regression/mcp-workspace-toggle.spec.ts index df7507bbecc0..58d29aab1efc 100644 --- a/packages/app/e2e/regression/mcp-workspace-toggle.spec.ts +++ b/packages/app/e2e/regression/mcp-workspace-toggle.spec.ts @@ -48,7 +48,7 @@ async function open( const panel = input.surface === "popover" - ? page.getByRole("dialog", { name: "MCP", exact: true }) + ? page.getByRole("group", { name: "MCP", exact: true }) : page.getByRole("dialog", { name: "MCPs", exact: true }) const show = async () => { @@ -122,10 +122,9 @@ test("the MCP popover shows a failed server and reconnects it from its details", }) const row = view.panel.locator(".session-mcp-row").filter({ hasText: "figma-desktop" }) - await expect(view.toggle).toHaveCount(0) - await expect(row.getByText("Failed", { exact: true })).toBeVisible() - const failed = view.panel.getByRole("button", { name: "Details for figma-desktop", exact: true }) - await expect(failed).toHaveText("Details") + // The red dot is the status, and opens the error. + await expect(view.toggle).toHaveAccessibleDescription("Failed") + const failed = row.getByRole("button", { name: "Details for figma-desktop", exact: true }) view.requests.length = 0 await failed.click() const details = page.getByRole("dialog", { name: "figma-desktop", exact: true }) @@ -138,7 +137,7 @@ test("the MCP popover shows a failed server and reconnects it from its details", ]) if (!(await view.panel.isVisible())) await view.show() - await expect(row.getByText("Connected", { exact: true })).toBeVisible() + await expect(view.toggle).toHaveAccessibleDescription("Connected") }) for (const surface of ["dialog"] as const) { @@ -280,12 +279,9 @@ for (const surface of ["popover", "dialog"] as const) { if (surface === "dialog") await expect(view.toggle).toBeChecked() else - await expect( - view.panel - .locator(".session-mcp-row") - .filter({ hasText: "figma-desktop" }) - .getByText("Connected", { exact: true }), - ).toBeVisible() + await expect(view.panel.getByRole("switch", { name: "figma-desktop", exact: true })).toHaveAccessibleDescription( + "Connected", + ) await expect(view.panel.getByRole("button", { name: "Sign in to figma-desktop", exact: true })).toHaveCount(0) }) } @@ -316,7 +312,11 @@ for (const surface of ["popover", "dialog"] as const) { .locator(surface === "dialog" ? '[role="option"]' : ".session-mcp-row") .filter({ hasText: name }) - const status = row.getByText(["Connected", "Disabled", "Failed", "Sign in"][index], { exact: true }) + // The popover's dot is the status, so the control beside the name must stay whole. + const status = + surface === "dialog" || index === 3 + ? row.getByText(["Connected", "Disabled", "Failed", "Sign in"][index], { exact: true }) + : row.locator('[data-slot="switch-control"]') await expect(status).toBeVisible() await expect diff --git a/packages/app/e2e/regression/mobile-extension-drawers.spec.ts b/packages/app/e2e/regression/mobile-extension-drawers.spec.ts index 43253738da8c..ef1a8089d325 100644 --- a/packages/app/e2e/regression/mobile-extension-drawers.spec.ts +++ b/packages/app/e2e/regression/mobile-extension-drawers.spec.ts @@ -43,7 +43,7 @@ for (const scheme of ["light", "dark"] as const) { await expect(page.getByRole("dialog")).toHaveCount(1) await expect(page.locator('[data-slot="mobile-drawer-overlay"]')).toHaveCount(1) await expect(extension.getByRole("button", { name: "Close", exact: true })).toHaveCount(0) - await expect(extension.locator(".session-service-menu")).toHaveCount(0) + await expect(extension.locator(".session-service-flyout")).toHaveCount(0) await expect(extension).toBeInViewport({ ratio: 1 }) await extension.getByRole("button", { name: "Navigate back", exact: true }).click() await expect(details.getByRole("button", { name: "MCP", exact: true })).toBeVisible() diff --git a/packages/app/e2e/regression/session-summary.spec.ts b/packages/app/e2e/regression/session-summary.spec.ts index 06856aa6bf4e..a88802717e17 100644 --- a/packages/app/e2e/regression/session-summary.spec.ts +++ b/packages/app/e2e/regression/session-summary.spec.ts @@ -305,7 +305,7 @@ for (const direction of ["ltr", "rtl"] as const) { await expectAlignedWithHeader(page, direction) const mcp = summary.getByRole("button", { name: "MCP", exact: true }) - const submenu = page.getByRole("dialog", { name: "MCP", exact: true }) + const submenu = page.getByRole("group", { name: "MCP", exact: true }) await mcp.click() await expect(submenu.getByText("No MCP servers configured", { exact: true })).toBeVisible() await expect(submenu.getByText("Configuration file")).toBeVisible() @@ -326,12 +326,12 @@ for (const direction of ["ltr", "rtl"] as const) { ["LSP", "No LSP servers configured"], ]) { await summary.getByRole("button", { name, exact: true }).click() - await expect(page.getByRole("dialog", { name, exact: true }).getByText(empty, { exact: true })).toBeVisible() + await expect(page.getByRole("group", { name, exact: true }).getByText(empty, { exact: true })).toBeVisible() await expect(submenu).toBeHidden() } await page.keyboard.press("Escape") - await expect(page.getByRole("dialog", { name: "LSP", exact: true })).toBeHidden() + await expect(page.getByRole("group", { name: "LSP", exact: true })).toBeHidden() await page.keyboard.press("Escape") await expect(summary).toBeHidden() @@ -414,7 +414,7 @@ test("summary catalogs load, refresh while cached, and tell errors from empty", await page.goto(sessionHref(fixture.targetID)) await page.getByRole("button", { name: "Session details", exact: true }).click() const summary = page.getByRole("dialog", { name: "Session details", exact: true }) - const menu = (name: string) => page.getByRole("dialog", { name, exact: true }) + const menu = (name: string) => page.getByRole("group", { name, exact: true }) const open = async (name: string) => { const item = summary.getByRole("button", { name, exact: true }) @@ -436,7 +436,13 @@ test("summary catalogs load, refresh while cached, and tell errors from empty", await menu("Plugins").getByRole("button", { name: "Retry", exact: true }).click() await expect(menu("Plugins").getByText("supermemory", { exact: true })).toBeVisible() await expect(menu("Plugins").getByText("builtin", { exact: true })).toHaveCount(0) - await expect(menu("Plugins").getByTitle("Plugin failed to activate")).toContainText("Failed") + // The dot is the status: a failed plugin's dot shows its error on hover. + await menu("Plugins") + .locator(".session-service-row") + .filter({ hasText: "broken-plugin" }) + .locator(".session-service-dot") + .hover() + await expect(page.getByRole("tooltip")).toHaveText("Plugin failed to activate") expect(pluginDirectories.length).toBeGreaterThan(1) expect(pluginDirectories.every((directory) => directory === fixture.directory)).toBe(true) @@ -445,7 +451,6 @@ test("summary catalogs load, refresh while cached, and tell errors from empty", await expect(menu("Skills").getByText("review-animations", { exact: true })).toBeVisible() await expect(menu("Plugins")).toBeHidden() await open("LSP") - await expect(menu("LSP").getByText("Configured LSPs", { exact: true })).toBeVisible() await expect(menu("LSP").getByText("typescript", { exact: true })).toBeVisible() await expect(menu("LSP").getByText("rust", { exact: true })).toHaveCount(0) await expect(menu("LSP").locator(".session-service-dot")).toHaveCount(0) @@ -503,7 +508,7 @@ test("MCP rows toggle once per action and keep sign-in, errors, and management a await page.getByRole("button", { name: "Session details", exact: true }).click() const item = page.getByRole("button", { name: "MCP", exact: true }) await item.click() - const submenu = page.getByRole("dialog", { name: "MCP", exact: true }) + const submenu = page.getByRole("group", { name: "MCP", exact: true }) await expect(submenu).toBeVisible() const row = (name: string) => @@ -511,23 +516,22 @@ test("MCP rows toggle once per action and keep sign-in, errors, and management a const connected = submenu.getByRole("switch", { name: "figma", exact: true }) const disabled = submenu.getByRole("switch", { name: "off", exact: true }) - await expect(submenu.getByRole("switch")).toHaveCount(3) + await expect(submenu.getByRole("switch")).toHaveCount(4) await expect(submenu.locator(".session-service-dot")).toHaveCount(5) await expect(connected).toBeChecked() await expect(disabled).not.toBeChecked() await expect(submenu.getByRole("switch", { name: "waiting", exact: true })).toBeDisabled() await expect(submenu.getByRole("switch", { name: "waiting", exact: true })).toHaveAccessibleDescription("Connecting…") - await expect(row("figma").getByText("Connected", { exact: true })).toBeVisible() - await expect( - row("waiting").locator('[data-slot="switch-label"]').getByText("Connecting…", { exact: true }), - ).toBeVisible() - await expect(row("off").getByText("Disabled", { exact: true })).toBeVisible() + // The dot is the status; the words are for assistive technology only. + await expect(connected).toHaveAccessibleDescription("Connected") + await expect(disabled).toHaveAccessibleDescription("Disabled") + await expect(submenu.getByRole("switch", { name: "playwright", exact: true })).toHaveAccessibleDescription("Failed") await expect(submenu.getByRole("button", { name: "Sign in to linear", exact: true })).toBeVisible() - await expect(row("playwright").getByText("Failed", { exact: true })).toBeVisible() + await expect(submenu.getByRole("button", { name: "Details for playwright", exact: true })).toBeVisible() await expect .poll(async () => { - const [heading, dot, label] = await Promise.all([ - submenu.getByText("Configured MCP servers", { exact: true }).evaluate((element) => { + const [inset, dot, label] = await Promise.all([ + row("figma").evaluate((element) => { const bounds = element.getBoundingClientRect() const style = getComputedStyle(element) @@ -548,14 +552,14 @@ test("MCP rows toggle once per action and keep sign-in, errors, and management a return { width: dot.width, height: dot.height, - alignment: heading.rtl ? heading.start - dot.x - dot.width : dot.x - heading.start, - gap: heading.rtl ? dot.x - label.x - label.width : label.x - dot.x - dot.width, + alignment: inset.rtl ? inset.start - dot.x - dot.width : dot.x - inset.start, + gap: inset.rtl ? dot.x - label.x - label.width : label.x - dot.x - dot.width, } }) .toEqual({ width: 6, height: 6, alignment: 0, gap: 8 }) await page.screenshot({ path: test.info().outputPath("mcp-toggles.png"), animations: "disabled" }) - for (const [index, target] of ["label", "dot", "padding", "control", "status", "keyboard"].entries()) { + for (const [index, target] of ["label", "dot", "padding", "control", "keyboard"].entries()) { await expect(connected).toBeEnabled() const pending = @@ -571,11 +575,6 @@ test("MCP rows toggle once per action and keep sign-in, errors, and management a if (target === "control") await row("figma").locator('[data-slot="switch-control"]').click() - if (target === "status") - await row("figma") - .getByText(index % 2 === 0 ? "Connected" : "Disabled", { exact: true }) - .click() - if (target === "keyboard") await connected.press("Space") if (pending) { @@ -586,7 +585,7 @@ test("MCP rows toggle once per action and keep sign-in, errors, and management a await expect(connected).toBeChecked({ checked: index % 2 !== 0 }) await expect(connected).toBeEnabled() - await expect(row("figma").getByText(index % 2 !== 0 ? "Connected" : "Disabled", { exact: true })).toBeVisible() + await expect(connected).toHaveAccessibleDescription(index % 2 !== 0 ? "Connected" : "Disabled") await expect(submenu).toBeVisible() expect(writes).toEqual( Array.from( @@ -601,13 +600,76 @@ test("MCP rows toggle once per action and keep sign-in, errors, and management a await expect(disabled).toBeChecked() await expect(disabled).toBeEnabled() expect(writes.at(-1)).toBe("/api/experimental/mcp/off/connect") - expect(writes).toHaveLength(7) + expect(writes).toHaveLength(6) await submenu.getByRole("button", { name: "Manage MCP servers", exact: true }).click() await expect(page.getByRole("dialog").getByText("MCPs", { exact: true })).toBeVisible() await expect(submenu).toBeHidden() }) +test("Extensions lists open on hover, hold course into the open list, and follow the keyboard", async ({ page }) => { + await mockStressTimeline(page, { + mcp: [{ name: "figma", status: { status: "connected" } }], + plugins: [ + { + id: "supermemory", + source: { type: "package", target: "opencode-supermemory" }, + features: {}, + state: { status: "active" }, + }, + ], + }) + await page.goto(sessionHref(fixture.targetID)) + await page.getByRole("button", { name: "Session details", exact: true }).click() + const summary = page.getByRole("dialog", { name: "Session details", exact: true }) + const row = (name: string) => summary.getByRole("button", { name, exact: true }) + const list = (name: string) => page.getByRole("group", { name, exact: true }) + + // Hovering opens a list without a click, and moving down the rows switches lists. + await row("MCP").hover() + await expect(row("MCP")).toHaveAttribute("aria-expanded", "true") + await expect(list("MCP").getByRole("switch", { name: "figma", exact: true })).toBeVisible() + await row("Plugins").hover() + await expect(list("Plugins").getByText("supermemory", { exact: true })).toBeVisible() + await expect(list("MCP")).toBeHidden() + await row("MCP").hover() + await expect(list("MCP")).toBeVisible() + + // A diagonal move from MCP into its list crosses the Plugins row without switching to it. + const from = (await row("MCP").boundingBox())! + const into = (await list("MCP").boundingBox())! + await page.mouse.move(into.x + into.width - 24, from.y + from.height * 3, { steps: 12 }) + await expect(row("MCP")).toHaveAttribute("aria-expanded", "true") + await expect(row("Plugins")).toHaveAttribute("aria-expanded", "false") + await expect(list("MCP")).toBeVisible() + + // A pointer that comes to rest on Plugins while still heading for the list switches to Plugins. + await page.mouse.move(from.x + from.width / 2, from.y + from.height / 2) + const plugins = (await row("Plugins").boundingBox())! + await page.mouse.move(plugins.x + 12, plugins.y + plugins.height / 2, { steps: 6 }) + await expect(list("Plugins")).toBeVisible() + + // The keyboard opens a list into its first control and closes it back to its row; Escape closes only the list. + await page.mouse.move(0, 0) + await page.keyboard.press("Escape") + await expect(list("Plugins")).toBeHidden() + await expect(summary).toBeVisible() + await row("MCP").focus() + await page.keyboard.press("ArrowRight") + await expect(list("MCP").getByRole("switch", { name: "figma", exact: true })).toBeFocused() + await page.keyboard.press("ArrowLeft") + await expect(list("MCP")).toBeHidden() + await expect(row("MCP")).toBeFocused() + await page.keyboard.press("Enter") + await expect(list("MCP").getByRole("switch", { name: "figma", exact: true })).toBeFocused() + await page.keyboard.press("Escape") + await expect(list("MCP")).toBeHidden() + await expect(row("MCP")).toBeFocused() + await expect(summary).toBeVisible() + await page.keyboard.press("Escape") + await expect(summary).toBeHidden() +}) + test("multiple desktop connections show the session's server name", async ({ page }) => { await mockStressTimeline(page) await page.route("http://secondary.test/**", (route) => @@ -669,7 +731,7 @@ test.describe("remote configuration", () => { await trigger.click() const item = summary.getByRole("button", { name: service.name, exact: true }) await item.click() - const menu = page.getByRole("dialog", { name: service.name, exact: true }) + const menu = page.getByRole("group", { name: service.name, exact: true }) await expect(menu).toBeVisible() const copy = menu.getByRole("button", { name: "Copy configuration file path", exact: true }) await expect(tooltip).toHaveCount(0) @@ -700,10 +762,10 @@ test.describe("remote configuration", () => { await trigger.click() const skills = summary.getByRole("button", { name: "Skills", exact: true }) await skills.click() - await expect(page.getByRole("dialog", { name: "Skills", exact: true })).toBeVisible() + await expect(page.getByRole("group", { name: "Skills", exact: true })).toBeVisible() const copy = page - .getByRole("dialog", { name: "Skills", exact: true }) + .getByRole("group", { name: "Skills", exact: true }) .getByRole("button", { name: "Copy configuration file path", exact: true }) await copy.click() @@ -726,7 +788,7 @@ test("long MCP lists stay in the viewport in a real RTL locale", async ({ page } await page.getByRole("button", { name: "פרטי ההפעלה", exact: true }).click() const summary = page.getByRole("dialog", { name: "פרטי ההפעלה", exact: true }) await summary.getByRole("button", { name: "MCP", exact: true }).click() - const menu = page.getByRole("dialog", { name: "MCP", exact: true }) + const menu = page.getByRole("group", { name: "MCP", exact: true }) await expect(menu.locator(".session-mcp-row")).toHaveCount(30) await expect.poll(() => menu.evaluate((element) => element.scrollHeight > element.clientHeight)).toBe(true) await expect @@ -776,7 +838,7 @@ async function expectAlignedWithHeader(page: Page, direction: "ltr" | "rtl") { // Reopens a service submenu while its next catalog request is held: the cached content stays, marked busy. async function expectRefreshKeeps(page: Page, name: string, path: string, content: string) { - const menu = page.getByRole("dialog", { name, exact: true }) + const menu = page.getByRole("group", { name, exact: true }) const summary = page.getByRole("dialog", { name: "Session details", exact: true }) const label = menu.getByText(content, { exact: true }).and(menu.locator(":not(.sr-only)")).filter({ visible: true }) await page.keyboard.press("Escape") diff --git a/packages/gui-extensions/src/details/details.css b/packages/gui-extensions/src/details/details.css index 482c3fc6f288..4658b53ba8d2 100644 --- a/packages/gui-extensions/src/details/details.css +++ b/packages/gui-extensions/src/details/details.css @@ -101,39 +101,167 @@ white-space: nowrap; text-align: start; } -.session-service-menu { +/* One flyout beside the Extensions card. It glides to the hovered row and clips to the list on show. */ +.session-service-flyout { + /* A .25s, bounce 0 spring, sampled over its .3s settle. */ + --spring-snap: linear( + 0, + 0.04, + 0.131, + 0.243, + 0.358, + 0.466, + 0.562, + 0.645, + 0.715, + 0.774, + 0.821, + 0.859, + 0.89, + 0.914, + 0.934, + 0.949, + 0.96, + 0.97, + 0.977, + 0.982, + 0.986, + 0.99, + 0.992, + 0.994, + 1 + ); + position: absolute; z-index: 60; + top: 0; + inset-inline-end: 100%; width: 280px; max-width: calc(100vw - 32px); - max-height: min(480px, calc(100dvh - 32px)); - overflow-y: auto; - overscroll-behavior: contain; + pointer-events: none; + visibility: hidden; + transition: + transform 0.3s var(--spring-snap), + visibility 0s linear 0.11s; + + &[data-open] { + pointer-events: auto; + visibility: visible; + transition: + transform 0.3s var(--spring-snap), + visibility 0s; + } +} + +.session-service-flyout-body { + /* The shadow follows the clipped surface, so it is a filter rather than a box shadow. */ + filter: drop-shadow(0 8px 8px var(--v2-alpha-dark-4)) drop-shadow(0 4px 4px var(--v2-alpha-dark-8)) + drop-shadow(0 0 0.5px var(--v2-alpha-dark-12)); + opacity: 0; + scale: 0.96; + translate: 4px 0; + transform-origin: right top; + transition: + opacity 0.11s cubic-bezier(0.4, 0, 1, 1), + scale 0.11s cubic-bezier(0.4, 0, 1, 1), + translate 0.11s cubic-bezier(0.4, 0, 1, 1); + + &:dir(rtl) { + translate: -4px 0; + transform-origin: left top; + } + + .session-service-flyout[data-open] & { + opacity: 1; + scale: 1; + translate: 0 0; + transition: + opacity 0.15s cubic-bezier(0.2, 0.8, 0.2, 1), + scale 0.15s cubic-bezier(0.2, 0.8, 0.2, 1), + translate 0.15s cubic-bezier(0.2, 0.8, 0.2, 1); + } +} + +[data-color-scheme="dark"] .session-service-flyout-body { + filter: drop-shadow(0 8px 8px var(--v2-alpha-dark-30)) drop-shadow(0 4px 4px var(--v2-alpha-dark-30)) + drop-shadow(0 0 0.5px var(--v2-alpha-light-16)); +} + +.session-service-flyout-surface { + display: grid; padding: 2px; border-radius: 6px; background: var(--v2-background-bg-layer-01); - box-shadow: var(--v2-elevation-floating); - outline: none; color: var(--v2-text-text-base); font-size: 13px; font-weight: 440; line-height: var(--line-height-compact); - transform-origin: var(--kb-popover-content-transform-origin); - animation: menu-v2-in 120ms ease-out; + clip-path: inset(0 0 calc(100% - var(--flyout-height)) 0 round 6px); + transition: clip-path 0.3s var(--spring-snap); +} + +.session-service-pane { + grid-area: 1 / 1; + align-self: start; + max-height: min(480px, calc(100dvh - 36px)); + overflow-y: auto; + overscroll-behavior: contain; + outline: none; + + /* Lists swap at once; only the box glides and resizes, so two lists never show together. */ + &:not([data-active]) { + visibility: hidden; + pointer-events: none; + } + + /* A list taller than the viewport scrolls under soft edges. */ + &[data-overflow] { + mask-image: linear-gradient(to bottom, transparent, #000 12px, #000 calc(100% - 16px), transparent); + + > .session-service-pane-content { + padding-block: 12px 16px; + } + } +} + +.session-service-pane [data-slot="truncated-text-label"] { + text-overflow: clip; + mask-image: linear-gradient(to right, #000 calc(100% - 20px), transparent); + + &:dir(rtl) { + mask-image: linear-gradient(to left, #000 calc(100% - 20px), transparent); + } +} - &[data-empty] { - width: 232px; +.session-service-trigger { + .session-service-dot { + width: 6px; } } -:is(.session-service-menu, .session-service-drawer) .session-summary-row, -:is(.session-service-menu, .session-service-drawer) .session-service-row, -:is(.session-service-menu, .session-service-drawer) .session-service-config, -:is(.session-service-menu, .session-service-drawer) .session-mcp-row { +.session-service-count { + flex-shrink: 0; + color: var(--v2-text-text-faint); + font-size: 12px; + font-variant-numeric: tabular-nums; +} + +.session-service-chevron:dir(rtl) { + scale: -1 1; +} + +.session-service-error { + overflow-wrap: anywhere; +} + +:is(.session-service-pane, .session-service-drawer) .session-summary-row, +:is(.session-service-pane, .session-service-drawer) .session-service-row, +:is(.session-service-pane, .session-service-drawer) .session-service-config, +:is(.session-service-pane, .session-service-drawer) .session-mcp-row { font-size: inherit; line-height: inherit; } -:is(.session-service-menu, .session-service-drawer) +:is(.session-service-pane-content, .session-service-drawer) > :is(.session-service-row, .session-mcp-row) + :is(.session-service-row, .session-mcp-row) { margin-block-start: 2px; @@ -145,17 +273,6 @@ line-height: var(--line-height-compact); } -.session-service-title { - display: flex; - align-items: center; - min-height: 32px; - padding: 0 12px; - color: var(--v2-text-text-faint); - font-size: inherit; - font-weight: 530; - letter-spacing: 0.05px; -} - .session-service-empty { display: flex; flex-direction: column; @@ -178,7 +295,7 @@ color: var(--v2-text-text-faint); overflow-wrap: anywhere; } -:is(.session-service-menu, .session-service-drawer) > .session-service-footer { +:is(.session-service-pane-content, .session-service-drawer) > .session-service-footer { padding-block-start: 6px; } .session-service-config-separator { @@ -239,6 +356,42 @@ .session-service-dot[data-status="needs_auth"] { color: var(--icon-warning-base); } +.session-service-dot[data-status="disabled"]::before { + background: transparent; + box-shadow: inset 0 0 0 1px currentColor; +} +.session-service-dot[data-status="pending"]::before { + animation: session-service-pulse 1s ease-in-out infinite; +} +@keyframes session-service-pulse { + 50% { + opacity: 0.35; + } +} +button.session-service-dot { + position: relative; + border-radius: 4px; + cursor: pointer; + + /* A 16px target around the dot, so a failed one is easy to hover. */ + &::after { + content: ""; + position: absolute; + inset: -5px; + } + + &:focus-visible { + outline: 2px solid var(--v2-border-border-focus); + outline-offset: 2px; + } +} +.session-service-dot-tip { + display: flex; + flex-shrink: 0; +} +:is(.session-mcp-row, .session-service-row)[data-status="disabled"] .session-summary-label { + color: var(--v2-text-text-muted); +} .session-mcp-name { display: flex; @@ -248,7 +401,7 @@ gap: 8px; } -.session-mcp-name > .session-service-dot { +.session-mcp-name .session-service-dot { width: 6px; height: 6px; } @@ -263,10 +416,6 @@ white-space: nowrap; } -.session-mcp-failed { - color: var(--v2-state-fg-danger); -} - div.session-mcp-row { cursor: pointer; } @@ -285,12 +434,6 @@ div.session-mcp-row { flex-direction: column; } -.session-service-status { - flex-shrink: 0; - font-size: inherit; - line-height: var(--line-height-compact); - color: var(--v2-text-text-faint); -} .session-mcp-row[aria-disabled="true"] [data-slot="switch-control"] { opacity: 0.5; } @@ -325,20 +468,39 @@ div.session-mcp-row { @media (max-width: 767px) { [data-component="session-summary-panel"] .session-summary-row, - .session-service-menu .session-service-title, - .session-service-menu .session-service-row, - .session-service-menu .session-service-config, - .session-service-menu .session-mcp-row { + .session-service-pane .session-service-row, + .session-service-pane .session-service-config, + .session-service-pane .session-mcp-row { min-height: 44px; } } :root[data-motion="reduce"] { .session-summary-popover, - .session-service-menu { + .session-service-dot::before { animation: none; } + /* No travel: the flyout and its lists only fade. */ + .session-service-flyout { + transition: visibility 0s linear 0.15s; + } + + .session-service-flyout[data-open] { + transition: visibility 0s; + } + + .session-service-flyout-surface { + transition: none; + } + + .session-service-flyout-body, + .session-service-flyout[data-open] .session-service-flyout-body { + scale: none; + translate: none; + transition: opacity 0.15s ease-out; + } + .session-service-config .session-service-config-arrow { transition: none; } diff --git a/packages/gui-extensions/src/details/i18n/am.ts b/packages/gui-extensions/src/details/i18n/am.ts index a74d13d06745..a63479ccf58b 100644 --- a/packages/gui-extensions/src/details/i18n/am.ts +++ b/packages/gui-extensions/src/details/i18n/am.ts @@ -8,16 +8,12 @@ export default { "move.failed": "ክፍለ ጊዜን ማንቀሳቀስ አልተሳካም", server: "ቅጥያዎች", mcp: "MCP", - "mcp.title": "የተዋቀሩ MCP አገልጋዮች", "mcp.empty": "ምንም የMCP አገልጋዮች አልተዋቀሩም", plugins: "ተሰኪዎች", - "plugins.configured": "የተዋቀሩ ተሰኪዎች", "plugins.empty": "ምንም ተሰኪዎች አልተዋቀሩ", skills: "ችሎታ", - "skills.configured": "የተዋቀሩ ክህሎቶች", "skills.empty": "ምንም ችሎታዎች አልተዋቀሩም", lsp: "LSP", - "lsp.configured": "የተዋቀሩ LSPs", "lsp.empty": "ምንም የLSP አገልጋዮች አልተዋቀሩም።", failed: "አልተሳካም", retry: "እንደገና ሞክር", diff --git a/packages/gui-extensions/src/details/i18n/ar.ts b/packages/gui-extensions/src/details/i18n/ar.ts index 75e59f3959ac..815620e64532 100644 --- a/packages/gui-extensions/src/details/i18n/ar.ts +++ b/packages/gui-extensions/src/details/i18n/ar.ts @@ -8,16 +8,12 @@ export default { "move.failed": "فشل نقل الجلسة", server: "الملحقات", mcp: "MCP", - "mcp.title": "خوادم MCP المكوّنة", "mcp.empty": "لم تُكوّن خوادم MCP", plugins: "الملحقات", - "plugins.configured": "الملحقات المكوّنة", "plugins.empty": "لم تُكوّن ملحقات", skills: "المهارات", - "skills.configured": "المهارات المكوّنة", "skills.empty": "لم تُكوّن مهارات", lsp: "LSP", - "lsp.configured": "خوادم LSP المكوّنة", "lsp.empty": "لم تُكوّن خوادم LSP", failed: "فشل", retry: "إعادة المحاولة", diff --git a/packages/gui-extensions/src/details/i18n/az.ts b/packages/gui-extensions/src/details/i18n/az.ts index 27a2418817a7..a282a342bc8a 100644 --- a/packages/gui-extensions/src/details/i18n/az.ts +++ b/packages/gui-extensions/src/details/i18n/az.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Sessiyanı köçürmək mümkün olmadı", server: "Genişlənmələr", mcp: "MCP", - "mcp.title": "Konfiqurasiya edilmiş MCP serverləri", "mcp.empty": "MCP serverləri konfiqurasiya edilməyib", plugins: "Pluginlər", - "plugins.configured": "Konfiqurasiya edilmiş pluginlər", "plugins.empty": "Heç bir plagin qurulmayıb", skills: "Bacarıqlar", - "skills.configured": "Konfiqurasiya edilmiş bacarıqlar", "skills.empty": "Heç bir bacarıq qurulmayıb", lsp: "LSP", - "lsp.configured": "Qurulmuş LSP-lər", "lsp.empty": "Heç bir LSP serveri qurulmayıb", failed: "Uğursuz oldu", retry: "Təkrar cəhd et", diff --git a/packages/gui-extensions/src/details/i18n/bg.ts b/packages/gui-extensions/src/details/i18n/bg.ts index 6f4adc379a92..e25ec4535cbb 100644 --- a/packages/gui-extensions/src/details/i18n/bg.ts +++ b/packages/gui-extensions/src/details/i18n/bg.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Преместването на сесията не бе успешно", server: "Разширения", mcp: "MCP", - "mcp.title": "Конфигурирани MCP сървъри", "mcp.empty": "Няма конфигурирани MCP сървъри", plugins: "Добавки", - "plugins.configured": "Конфигурирани добавки", "plugins.empty": "Няма конфигурирани добавки", skills: "Умения", - "skills.configured": "Конфигурирани умения", "skills.empty": "Няма конфигурирани умения", lsp: "LSP", - "lsp.configured": "Конфигурирани LSP сървъри", "lsp.empty": "Няма конфигурирани LSP сървъри", failed: "Неуспешно", retry: "Повторен опит", diff --git a/packages/gui-extensions/src/details/i18n/bn.ts b/packages/gui-extensions/src/details/i18n/bn.ts index 2eb692895cb5..60a11fafd0d0 100644 --- a/packages/gui-extensions/src/details/i18n/bn.ts +++ b/packages/gui-extensions/src/details/i18n/bn.ts @@ -8,16 +8,12 @@ export default { "move.failed": "সেশন সরানো ব্যর্থ হয়েছে", server: "এক্সটেনশন", mcp: "MCP", - "mcp.title": "কনফিগার করা MCP সার্ভার", "mcp.empty": "কোনো MCP সার্ভার কনফিগার করা হয়নি৷", plugins: "প্লাগইন", - "plugins.configured": "কনফিগার করা প্লাগইন", "plugins.empty": "কোনো প্লাগইন কনফিগার করা নেই", skills: "দক্ষতা", - "skills.configured": "কনফিগার করা দক্ষতা", "skills.empty": "কোন দক্ষতা কনফিগার করা", lsp: "LSP", - "lsp.configured": "কনফিগার করা LSP", "lsp.empty": "কোনো LSP সার্ভার কনফিগার করা হয়নি৷", failed: "ব্যর্থ হয়েছে", retry: "আবার চেষ্টা করুন", diff --git a/packages/gui-extensions/src/details/i18n/br.ts b/packages/gui-extensions/src/details/i18n/br.ts index 787c9412652c..001f8e913534 100644 --- a/packages/gui-extensions/src/details/i18n/br.ts +++ b/packages/gui-extensions/src/details/i18n/br.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Falha ao mover sessão", server: "Extensões", mcp: "MCP", - "mcp.title": "Servidores MCP configurados", "mcp.empty": "Nenhum servidor MCP configurado", plugins: "Plug-ins", - "plugins.configured": "Plug-ins configurados", "plugins.empty": "Nenhum plug-in configurado", skills: "Habilidades", - "skills.configured": "Habilidades configuradas", "skills.empty": "Nenhuma habilidade configurada", lsp: "LSP", - "lsp.configured": "LSPs configurado", "lsp.empty": "Nenhum servidor LSP configurado", failed: "Falha", retry: "Tentar novamente", diff --git a/packages/gui-extensions/src/details/i18n/bs.ts b/packages/gui-extensions/src/details/i18n/bs.ts index b94fd6fc72b5..5df4a5c90555 100644 --- a/packages/gui-extensions/src/details/i18n/bs.ts +++ b/packages/gui-extensions/src/details/i18n/bs.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Premještanje sesije nije uspjelo", server: "Proširenja", mcp: "MCP", - "mcp.title": "Konfigurirani MCP serveri", "mcp.empty": "Nema konfiguriranih MCP servera", plugins: "Dodaci", - "plugins.configured": "Konfigurirani dodaci", "plugins.empty": "Nema konfiguriranih dodataka", skills: "Vještine", - "skills.configured": "Konfigurirane vještine", "skills.empty": "Nema konfiguriranih vještina", lsp: "LSP", - "lsp.configured": "Konfigurirani LSP-ovi", "lsp.empty": "Nema konfiguriranih LSP servera", failed: "Neuspješno", retry: "Pokušaj ponovo", diff --git a/packages/gui-extensions/src/details/i18n/ca.ts b/packages/gui-extensions/src/details/i18n/ca.ts index 55a02d0fbd10..19f7fe7911cd 100644 --- a/packages/gui-extensions/src/details/i18n/ca.ts +++ b/packages/gui-extensions/src/details/i18n/ca.ts @@ -8,16 +8,12 @@ export default { "move.failed": "No s'ha pogut moure la sessió", server: "Extensions", mcp: "MCP", - "mcp.title": "Servidors MCP configurats", "mcp.empty": "No s'ha configurat cap servidor MCP", plugins: "Connectors", - "plugins.configured": "Connectors configurats", "plugins.empty": "No s'ha configurat cap connector", skills: "Habilitats", - "skills.configured": "Habilitats configurades", "skills.empty": "No s'ha configurat cap habilitat", lsp: "LSP", - "lsp.configured": "LSPs configurat", "lsp.empty": "No s'ha configurat cap servidor LSP", failed: "Ha fallat", retry: "Torna-ho a provar", diff --git a/packages/gui-extensions/src/details/i18n/cs.ts b/packages/gui-extensions/src/details/i18n/cs.ts index ea36eb52c727..fbc569b13dfb 100644 --- a/packages/gui-extensions/src/details/i18n/cs.ts +++ b/packages/gui-extensions/src/details/i18n/cs.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Přesun relace se nezdařil", server: "Rozšíření", mcp: "MCP", - "mcp.title": "Nakonfigurované servery MCP", "mcp.empty": "Nejsou nakonfigurovány žádné servery MCP", plugins: "Zásuvné moduly", - "plugins.configured": "Nakonfigurované zásuvné moduly", "plugins.empty": "Nejsou nakonfigurovány žádné zásuvné moduly", skills: "Dovednosti", - "skills.configured": "Nakonfigurované dovednosti", "skills.empty": "Nejsou nakonfigurovány žádné dovednosti", lsp: "LSP", - "lsp.configured": "Nakonfigurovaná LSP", "lsp.empty": "Nejsou nakonfigurovány žádné servery LSP", failed: "Nezdařilo se", retry: "Zkusit znovu", diff --git a/packages/gui-extensions/src/details/i18n/da.ts b/packages/gui-extensions/src/details/i18n/da.ts index b2dea7b6529f..a8f35559165a 100644 --- a/packages/gui-extensions/src/details/i18n/da.ts +++ b/packages/gui-extensions/src/details/i18n/da.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Sessionen kunne ikke flyttes", server: "Udvidelser", mcp: "MCP", - "mcp.title": "Konfigurerede MCP-servere", "mcp.empty": "Ingen MCP-servere konfigureret", plugins: "Plugins", - "plugins.configured": "Konfigurerede plugins", "plugins.empty": "Ingen plugins konfigureret", skills: "Færdigheder", - "skills.configured": "Konfigurerede færdigheder", "skills.empty": "Ingen færdigheder konfigureret", lsp: "LSP", - "lsp.configured": "Konfigureret LSPs", "lsp.empty": "Ingen LSP-servere konfigureret", failed: "Mislykkedes", retry: "Prøv igen", diff --git a/packages/gui-extensions/src/details/i18n/de.ts b/packages/gui-extensions/src/details/i18n/de.ts index dacfa841dbca..f6e0d1c27849 100644 --- a/packages/gui-extensions/src/details/i18n/de.ts +++ b/packages/gui-extensions/src/details/i18n/de.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Sitzung konnte nicht verschoben werden", server: "Erweiterungen", mcp: "MCP", - "mcp.title": "Konfigurierte MCP-Server", "mcp.empty": "Keine MCP-Server konfiguriert", plugins: "Plugins", - "plugins.configured": "Konfigurierte Plugins", "plugins.empty": "Keine Plugins konfiguriert", skills: "Fähigkeiten", - "skills.configured": "Konfigurierte Fähigkeiten", "skills.empty": "Keine Fähigkeiten konfiguriert", lsp: "LSP", - "lsp.configured": "Konfiguriert LSPs", "lsp.empty": "Keine LSP-Server konfiguriert", failed: "Fehlgeschlagen", retry: "Versuchen Sie es noch einmal", diff --git a/packages/gui-extensions/src/details/i18n/dv.ts b/packages/gui-extensions/src/details/i18n/dv.ts index 7e10d247fbf6..f28bb0c565dc 100644 --- a/packages/gui-extensions/src/details/i18n/dv.ts +++ b/packages/gui-extensions/src/details/i18n/dv.ts @@ -8,16 +8,12 @@ export default { "move.failed": "ސެޝަން ބަދަލު ނުކުރެވުނެވެ", server: "އިތުރުކުރުން", mcp: "MCP", - "mcp.title": "ކޮންފިގްރޭޓް ކުރެވިފައިވާ MCP ސަރވަރސް", "mcp.empty": "MCP ސަރވަރ ކޮންފިގްރޭޓް ކުރެވިފައެއް ނުވެއެވެ", plugins: "ޕްލަގިންސް އެވެ", - "plugins.configured": "ކޮންފިގްރޭޓް ކުރެވިފައިވާ ޕްލަގިންސް", "plugins.empty": "ޕްލަގިންސް ކޮންފިގްރޭޓް ކުރެވިފައެއް ނުވެއެވެ", skills: "ހުނަރުތައް", - "skills.configured": "ކޮންފިގްރޭޓް ކުރެވިފައިވާ ހުނަރުތައް", "skills.empty": "އެއްވެސް ހުނަރެއް ކޮންފިގްރޭޓް ކުރެވިފައެއް ނުވެއެވެ", lsp: "LSP", - "lsp.configured": "ކޮންފިގްރޭޓް ކުރެވިފައިވާ އެލްއެސްޕީތަކެވެ", "lsp.empty": "LSP ސަރވަރ ކޮންފިގްރޭޓް ކުރެވިފައެއް ނުވެއެވެ", failed: "ފެއިލްވުން", retry: "އަލުން މަސައްކަތް ކުރާށެވެ", diff --git a/packages/gui-extensions/src/details/i18n/dz.ts b/packages/gui-extensions/src/details/i18n/dz.ts index 8f3ff76c469b..5c31fb549fc6 100644 --- a/packages/gui-extensions/src/details/i18n/dz.ts +++ b/packages/gui-extensions/src/details/i18n/dz.ts @@ -8,16 +8,12 @@ export default { "move.failed": "ལཱ་ཡུན་སྤོ་མ་ཚུགས།", server: "རྒྱ་བསྐྱེད་", mcp: "MCP", - "mcp.title": "རིམ་སྒྲིག་འབད་ཡོད་པའི་ MCP སར་བར་ཚུ་", "mcp.empty": "MCP སར་བར་ཚུ་རིམ་སྒྲིག་མ་འབད་བས།", plugins: "པ་ལག་ཨིན་", - "plugins.configured": "རིམ་སྒྲིག་འབད་ཡོད་པའི་པ་ལག་ཨིན་ཚུ་", "plugins.empty": "པ་ལག་ཨིན་ཚུ་རིམ་སྒྲིག་མ་འབད་བས།", skills: "རིག་རྩལ།", - "skills.configured": "རིམ་སྒྲིག་འབད་ཡོད་པའི་རིག་རྩལ།", "skills.empty": "རིག་རྩལ་རིམ་སྒྲིག་མ་འབད་བས།", lsp: "LSP", - "lsp.configured": "རིམ་སྒྲིག་འབད་ཡོད་པའི་ LSPs", "lsp.empty": "LSP སར་བར་ཚུ་རིམ་སྒྲིག་མ་འབད་བས།", failed: "འཐུས་ཤོར་བྱུང་ཡོདཔ།", retry: "བསྐྱར་དུ་ཚོད་ལྟ་", diff --git a/packages/gui-extensions/src/details/i18n/el.ts b/packages/gui-extensions/src/details/i18n/el.ts index b2c6574602c7..04569c8cfe80 100644 --- a/packages/gui-extensions/src/details/i18n/el.ts +++ b/packages/gui-extensions/src/details/i18n/el.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Αποτυχία μετακίνησης συνεδρίας", server: "Επεκτάσεις", mcp: "MCP", - "mcp.title": "Διαμορφωμένοι διακομιστές MCP", "mcp.empty": "Δεν έχουν διαμορφωθεί διακομιστές MCP", plugins: "Πρόσθετα", - "plugins.configured": "Ρυθμισμένες προσθήκες", "plugins.empty": "Δεν έχουν ρυθμιστεί πρόσθετα", skills: "Δεξιότητες", - "skills.configured": "Διαμορφωμένες δεξιότητες", "skills.empty": "Δεν έχουν διαμορφωθεί δεξιότητες", lsp: "LSP", - "lsp.configured": "Διαμόρφωσε το LSPs", "lsp.empty": "Δεν έχουν διαμορφωθεί διακομιστές LSP", failed: "Απέτυχε", retry: "Επανάληψη", diff --git a/packages/gui-extensions/src/details/i18n/en.ts b/packages/gui-extensions/src/details/i18n/en.ts index 446b17ed2e83..286b0a85a601 100644 --- a/packages/gui-extensions/src/details/i18n/en.ts +++ b/packages/gui-extensions/src/details/i18n/en.ts @@ -8,17 +8,13 @@ export default { "move.failed": "Failed to move session", server: "Extensions", mcp: "MCP", - "mcp.title": "Configured MCP servers", "mcp.empty": "No MCP servers configured", "mcp.manage": "Manage MCP servers", plugins: "Plugins", - "plugins.configured": "Configured plugins", "plugins.empty": "No plugins configured", skills: "Skills", - "skills.configured": "Configured skills", "skills.empty": "No skills configured", lsp: "LSP", - "lsp.configured": "Configured LSPs", "lsp.empty": "No LSP servers configured", failed: "Failed", retry: "Retry", diff --git a/packages/gui-extensions/src/details/i18n/es.ts b/packages/gui-extensions/src/details/i18n/es.ts index cc9c43031007..f67b95012a49 100644 --- a/packages/gui-extensions/src/details/i18n/es.ts +++ b/packages/gui-extensions/src/details/i18n/es.ts @@ -8,16 +8,12 @@ export default { "move.failed": "No se pudo mover la sesión", server: "Extensiones", mcp: "MCP", - "mcp.title": "Servidores MCP configurados", "mcp.empty": "No hay servidores MCP configurados", plugins: "Complementos", - "plugins.configured": "Complementos configurados", "plugins.empty": "No hay complementos configurados", skills: "Habilidades", - "skills.configured": "Habilidades configuradas", "skills.empty": "No hay habilidades configuradas", lsp: "LSP", - "lsp.configured": "Configurado LSPs", "lsp.empty": "No hay servidores LSP configurados", failed: "Fallido", retry: "Reintentar", diff --git a/packages/gui-extensions/src/details/i18n/et.ts b/packages/gui-extensions/src/details/i18n/et.ts index 334b15432fc8..5257b0f37279 100644 --- a/packages/gui-extensions/src/details/i18n/et.ts +++ b/packages/gui-extensions/src/details/i18n/et.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Seansi teisaldamine ebaõnnestus", server: "Laiendused", mcp: "MCP", - "mcp.title": "Konfigureeritud MCP serverid", "mcp.empty": "MCP servereid pole konfigureeritud", plugins: "Pluginad", - "plugins.configured": "Konfigureeritud pluginad", "plugins.empty": "Ühtegi pluginat pole seadistatud", skills: "Oskused", - "skills.configured": "Konfigureeritud oskused", "skills.empty": "Oskusi pole konfigureeritud", lsp: "LSP", - "lsp.configured": "Konfigureeritud LSPs", "lsp.empty": "LSP servereid pole konfigureeritud", failed: "Ebaõnnestunud", retry: "Proovi uuesti", diff --git a/packages/gui-extensions/src/details/i18n/fa.ts b/packages/gui-extensions/src/details/i18n/fa.ts index 16f507e28c8d..6354221579bd 100644 --- a/packages/gui-extensions/src/details/i18n/fa.ts +++ b/packages/gui-extensions/src/details/i18n/fa.ts @@ -8,16 +8,12 @@ export default { "move.failed": "انتقال جلسه ناموفق بود", server: "افزونه‌ها", mcp: "MCP", - "mcp.title": "سرورهای MCP پیکربندی‌شده", "mcp.empty": "هیچ سرور MCPای پیکربندی نشده است", plugins: "افزونه‌ها", - "plugins.configured": "افزونه‌های پیکربندی‌شده", "plugins.empty": "هیچ افزونه‌ای پیکربندی نشده است", skills: "مهارت‌ها", - "skills.configured": "مهارت‌های پیکربندی‌شده", "skills.empty": "هیچ مهارتی پیکربندی نشده است", lsp: "LSP", - "lsp.configured": "LSPهای پیکربندی‌شده", "lsp.empty": "هیچ سرور LSPای پیکربندی نشده است", failed: "ناموفق", retry: "تلاش دوباره", diff --git a/packages/gui-extensions/src/details/i18n/fi.ts b/packages/gui-extensions/src/details/i18n/fi.ts index e7f1cc7084e2..c31f1c47718e 100644 --- a/packages/gui-extensions/src/details/i18n/fi.ts +++ b/packages/gui-extensions/src/details/i18n/fi.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Istunnon siirtäminen epäonnistui", server: "Laajennukset", mcp: "MCP", - "mcp.title": "Konfiguroidut MCP-palvelimet", "mcp.empty": "MCP-palvelimia ei ole määritetty", plugins: "Plugins", - "plugins.configured": "Määritetyt laajennukset", "plugins.empty": "Mitään laajennuksia ei ole määritetty", skills: "Taidot", - "skills.configured": "Määritetyt taidot", "skills.empty": "Ei määritettyjä taitoja", lsp: "LSP", - "lsp.configured": "Konfiguroitu LSPs", "lsp.empty": "LSP-palvelimia ei ole määritetty", failed: "Epäonnistui", retry: "Yritä uudelleen", diff --git a/packages/gui-extensions/src/details/i18n/fo.ts b/packages/gui-extensions/src/details/i18n/fo.ts index fcdaff2259ad..b9a17a5cafb5 100644 --- a/packages/gui-extensions/src/details/i18n/fo.ts +++ b/packages/gui-extensions/src/details/i18n/fo.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Tað eydnaðist ikki at flyta setuna", server: "Útbyggingar", mcp: "MCP", - "mcp.title": "Uppsettir MCP ambætarar", "mcp.empty": "Eingin MCP ambætari er uppsettur", plugins: "Tilskot", - "plugins.configured": "Uppsett tilskot", "plugins.empty": "Eingi tilskot uppsett", skills: "Førleikar", - "skills.configured": "Uppsettir førleikar", "skills.empty": "Eingin førleiki er uppsettur", lsp: "LSP", - "lsp.configured": "Uppsett LSPs", "lsp.empty": "Eingin LSP ambætari er uppsettur", failed: "Mistókst", retry: "Royn aftur", diff --git a/packages/gui-extensions/src/details/i18n/fr.ts b/packages/gui-extensions/src/details/i18n/fr.ts index 1fd3217e8d7e..fe924424c959 100644 --- a/packages/gui-extensions/src/details/i18n/fr.ts +++ b/packages/gui-extensions/src/details/i18n/fr.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Échec du déplacement de la session", server: "Rallonges", mcp: "MCP", - "mcp.title": "Serveurs MCP configurés", "mcp.empty": "Aucun serveur MCP configuré", plugins: "Plugins", - "plugins.configured": "Plugins configurés", "plugins.empty": "Aucun plugin configuré", skills: "Compétences", - "skills.configured": "Compétences configurées", "skills.empty": "Aucune compétence configurée", lsp: "LSP", - "lsp.configured": "LSPs configuré", "lsp.empty": "Aucun serveur LSP configuré", failed: "Échec", retry: "Réessayer", diff --git a/packages/gui-extensions/src/details/i18n/he.ts b/packages/gui-extensions/src/details/i18n/he.ts index 0a2dba4befc2..a44fc8d8e058 100644 --- a/packages/gui-extensions/src/details/i18n/he.ts +++ b/packages/gui-extensions/src/details/i18n/he.ts @@ -8,16 +8,12 @@ export default { "move.failed": "העברת ההפעלה נכשלה", server: "הרחבות", mcp: "MCP", - "mcp.title": "שרתי MCP מוגדרים", "mcp.empty": "לא הוגדרו שרתי MCP", plugins: "תוספים", - "plugins.configured": "תוספים מוגדרים", "plugins.empty": "לא הוגדרו תוספים", skills: "מיומנויות", - "skills.configured": "מיומנויות מוגדרות", "skills.empty": "לא הוגדרו מיומנויות", lsp: "LSP", - "lsp.configured": "שרתי LSP מוגדרים", "lsp.empty": "לא הוגדרו שרתי LSP", failed: "נכשל", retry: "ניסיון חוזר", diff --git a/packages/gui-extensions/src/details/i18n/hi.ts b/packages/gui-extensions/src/details/i18n/hi.ts index 0f7b47ff37e1..b0b87860de37 100644 --- a/packages/gui-extensions/src/details/i18n/hi.ts +++ b/packages/gui-extensions/src/details/i18n/hi.ts @@ -8,16 +8,12 @@ export default { "move.failed": "सेशन ले जाने में विफल", server: "एक्सटेंशन", mcp: "MCP", - "mcp.title": "कॉन्फ़िगर किए गए MCP सर्वर", "mcp.empty": "कोई MCP सर्वर कॉन्फ़िगर नहीं किया गया", plugins: "प्लग-इन", - "plugins.configured": "कॉन्फ़िगर किए गए प्लगइन्स", "plugins.empty": "कोई प्लगइन्स कॉन्फ़िगर नहीं किया गया", skills: "कौशल", - "skills.configured": "कॉन्फ़िगर कौशल", "skills.empty": "कोई कौशल कॉन्फ़िगर नहीं किया गया", lsp: "LSP", - "lsp.configured": "विन्यस्त एलएसपी", "lsp.empty": "कोई LSP सर्वर कॉन्फ़िगर नहीं किया गया", failed: "असफल", retry: "पुन: प्रयास करें", diff --git a/packages/gui-extensions/src/details/i18n/hr.ts b/packages/gui-extensions/src/details/i18n/hr.ts index c3bfa04f9a01..1a5126b10e00 100644 --- a/packages/gui-extensions/src/details/i18n/hr.ts +++ b/packages/gui-extensions/src/details/i18n/hr.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Premještanje sesije nije uspjelo", server: "Proširenja", mcp: "MCP", - "mcp.title": "Konfigurirani MCP poslužitelji", "mcp.empty": "Nema konfiguriranih MCP poslužitelja", plugins: "Dodaci", - "plugins.configured": "Konfigurirani dodaci", "plugins.empty": "Nema konfiguriranih dodataka", skills: "Vještine", - "skills.configured": "Konfigurirane vještine", "skills.empty": "Nema konfiguriranih vještina", lsp: "LSP", - "lsp.configured": "Konfigurirani LSP-ovi", "lsp.empty": "Nema konfiguriranih LSP poslužitelja", failed: "Neuspješno", retry: "Pokušaj ponovno", diff --git a/packages/gui-extensions/src/details/i18n/hu.ts b/packages/gui-extensions/src/details/i18n/hu.ts index 12f2d571a6b6..76d2d5637dbe 100644 --- a/packages/gui-extensions/src/details/i18n/hu.ts +++ b/packages/gui-extensions/src/details/i18n/hu.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Nem sikerült áthelyezni a munkamenetet", server: "Kiterjesztések", mcp: "MCP", - "mcp.title": "Konfigurált MCP szerverek", "mcp.empty": "Nincs konfigurálva MCP szerver", plugins: "Beépülő modulok", - "plugins.configured": "Beállított bővítmények", "plugins.empty": "Nincsenek beállítva bővítmények", skills: "Készségek", - "skills.configured": "Konfigurált készségek", "skills.empty": "Nincsenek konfigurálva készségek", lsp: "LSP", - "lsp.configured": "Konfigurált LSPs", "lsp.empty": "Nincs konfigurálva LSP szerver", failed: "Sikertelen", retry: "Próbálja újra", diff --git a/packages/gui-extensions/src/details/i18n/hy.ts b/packages/gui-extensions/src/details/i18n/hy.ts index c5616e7961c1..835132e8aa46 100644 --- a/packages/gui-extensions/src/details/i18n/hy.ts +++ b/packages/gui-extensions/src/details/i18n/hy.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Չհաջողվեց տեղափոխել նիստը", server: "Ընդլայնումներ", mcp: "MCP", - "mcp.title": "Կարգավորված MCP սերվերներ", "mcp.empty": "MCP սերվերները կոնֆիգուրացված չեն", plugins: "Հավելվածներ", - "plugins.configured": "Կարգավորված հավելվածներ", "plugins.empty": "Պլագիններ չեն կոնֆիգուրացված", skills: "Հմտություններ", - "skills.configured": "Կարգավորված հմտություններ", "skills.empty": "Ունակություններ չեն կոնֆիգուրացված", lsp: "LSP", - "lsp.configured": "Կոնֆիգուրացված LSP-ներ", "lsp.empty": "LSP սերվերներ չեն կոնֆիգուրացված", failed: "Վստահեցումը ձախողվեց", retry: "Փորձել կրկին", diff --git a/packages/gui-extensions/src/details/i18n/id.ts b/packages/gui-extensions/src/details/i18n/id.ts index a796e7af769a..1b37d4088050 100644 --- a/packages/gui-extensions/src/details/i18n/id.ts +++ b/packages/gui-extensions/src/details/i18n/id.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Gagal memindahkan sesi", server: "Ekstensi", mcp: "MCP", - "mcp.title": "Server MCP yang dikonfigurasi", "mcp.empty": "Tidak ada server MCP yang dikonfigurasi", plugins: "Plugin", - "plugins.configured": "Plugin yang dikonfigurasi", "plugins.empty": "Tidak ada plugin yang dikonfigurasi", skills: "Keahlian", - "skills.configured": "Keahlian yang dikonfigurasi", "skills.empty": "Tidak ada keahlian yang dikonfigurasi", lsp: "LSP", - "lsp.configured": "LSP yang dikonfigurasi", "lsp.empty": "Tidak ada server LSP yang dikonfigurasi", failed: "Gagal", retry: "Coba lagi", diff --git a/packages/gui-extensions/src/details/i18n/is.ts b/packages/gui-extensions/src/details/i18n/is.ts index 3706ca55b645..e55efeb2ede0 100644 --- a/packages/gui-extensions/src/details/i18n/is.ts +++ b/packages/gui-extensions/src/details/i18n/is.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Mistókst að færa setu", server: "Viðbætur", mcp: "MCP", - "mcp.title": "Stilltir MCP netþjónar", "mcp.empty": "Engir MCP netþjónar stilltir", plugins: "Viðbætur", - "plugins.configured": "Stillt viðbætur", "plugins.empty": "Engar viðbætur stilltar", skills: "Færni", - "skills.configured": "Stilla færni", "skills.empty": "Engin færni stillt", lsp: "LSP", - "lsp.configured": "Stillt LSPs", "lsp.empty": "Engir LSP netþjónar stilltir", failed: "Mistókst", retry: "Reyndu aftur", diff --git a/packages/gui-extensions/src/details/i18n/it.ts b/packages/gui-extensions/src/details/i18n/it.ts index d11e0fd78b4f..aa3ec9d73f15 100644 --- a/packages/gui-extensions/src/details/i18n/it.ts +++ b/packages/gui-extensions/src/details/i18n/it.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Impossibile spostare la sessione", server: "Estensioni", mcp: "MCP", - "mcp.title": "Server MCP configurati", "mcp.empty": "Nessun server MCP configurato", plugins: "Plugin", - "plugins.configured": "Plugin configurati", "plugins.empty": "Nessun plugin configurato", skills: "Abilità", - "skills.configured": "Competenze configurate", "skills.empty": "Nessuna competenza configurata", lsp: "LSP", - "lsp.configured": "LSPs configurato", "lsp.empty": "Nessun server LSP configurato", failed: "Fallito", retry: "Riprova", diff --git a/packages/gui-extensions/src/details/i18n/ja.ts b/packages/gui-extensions/src/details/i18n/ja.ts index 702bda5d6b0a..b0444d2011bc 100644 --- a/packages/gui-extensions/src/details/i18n/ja.ts +++ b/packages/gui-extensions/src/details/i18n/ja.ts @@ -8,16 +8,12 @@ export default { "move.failed": "セッションの移動に失敗しました", server: "拡張機能", mcp: "MCP", - "mcp.title": "設定済みのMCPサーバー", "mcp.empty": "MCPサーバーが設定されていません", plugins: "プラグイン", - "plugins.configured": "設定済みのプラグイン", "plugins.empty": "プラグインが設定されていません", skills: "スキル", - "skills.configured": "設定済みのスキル", "skills.empty": "スキルが設定されていません", lsp: "LSP", - "lsp.configured": "設定済みのLSP", "lsp.empty": "LSPサーバーが設定されていません", failed: "失敗", retry: "再試行", diff --git a/packages/gui-extensions/src/details/i18n/ka.ts b/packages/gui-extensions/src/details/i18n/ka.ts index c5c375340d56..9f0c342f42f7 100644 --- a/packages/gui-extensions/src/details/i18n/ka.ts +++ b/packages/gui-extensions/src/details/i18n/ka.ts @@ -8,16 +8,12 @@ export default { "move.failed": "სესიის გადატანა ვერ მოხერხდა", server: "გაფართოებები", mcp: "MCP", - "mcp.title": "კონფიგურირებული MCP სერვერები", "mcp.empty": "MCP სერვერები კონფიგურირებული არაა", plugins: "პულგინები", - "plugins.configured": "კონფიგურირებული პულგინები", "plugins.empty": "პლაგინები არაა კონფიგურებული", skills: "უნარები", - "skills.configured": "კონფიგურირებული უნარები", "skills.empty": "უნარები არაა კონფიგურებული", lsp: "LSP", - "lsp.configured": "კონფიგურირებული LSP-ები", "lsp.empty": "LSP სერვერები არაა კონფიგურირებული", failed: "ტექნიკური წარუმატებელი", retry: "ხელახლა სცადეთ", diff --git a/packages/gui-extensions/src/details/i18n/km.ts b/packages/gui-extensions/src/details/i18n/km.ts index 00a32013f7d9..b3bfafb7510f 100644 --- a/packages/gui-extensions/src/details/i18n/km.ts +++ b/packages/gui-extensions/src/details/i18n/km.ts @@ -8,16 +8,12 @@ export default { "move.failed": "បរាជ័យក្នុងការផ្លាស់ទីសម័យ", server: "ផ្នែកបន្ថែម", mcp: "MCP", - "mcp.title": "ម៉ាស៊ីនមេ MCP ដែលបានកំណត់រចនាសម្ព័ន្ធ", "mcp.empty": "មិនមានម៉ាស៊ីន​បម្រើ MCP ត្រូវបានកំណត់", plugins: "ផ្លុយអ៊ីន", - "plugins.configured": "ផ្លុយអ៊ីនដែលបានកំណត់រចនាសម្ព័ន្ធ", "plugins.empty": "មិនមានផ្លាតថ្ន័ណាមួយត្រូវបានកំណត់", skills: "ជំនាញ", - "skills.configured": "ជំនាញបានកំណត់", "skills.empty": "មិនមានជំនាញដែលបានកំណត់ទេ", lsp: "LSP", - "lsp.configured": "LSPs ដែលបានកំណត់", "lsp.empty": "មិនមានម៉ាស៊ីនមេ LSP ត្រូវបានកំណត់ទេ", failed: "បរាជ័យ", retry: "សាកលើកម្តងទៀត", diff --git a/packages/gui-extensions/src/details/i18n/ko.ts b/packages/gui-extensions/src/details/i18n/ko.ts index 17dbb355a396..5f31a1ea24ab 100644 --- a/packages/gui-extensions/src/details/i18n/ko.ts +++ b/packages/gui-extensions/src/details/i18n/ko.ts @@ -8,16 +8,12 @@ export default { "move.failed": "세션 이동 실패", server: "확장 기능", mcp: "MCP", - "mcp.title": "구성된 MCP 서버", "mcp.empty": "구성된 MCP 서버가 없습니다", plugins: "플러그인", - "plugins.configured": "구성된 플러그인", "plugins.empty": "구성된 플러그인이 없습니다", skills: "스킬", - "skills.configured": "구성된 스킬", "skills.empty": "설정된 스킬이 없습니다", lsp: "LSP", - "lsp.configured": "설정된 LSP", "lsp.empty": "설정된 LSP 서버가 없습니다", failed: "실패", retry: "재시도", diff --git a/packages/gui-extensions/src/details/i18n/lo.ts b/packages/gui-extensions/src/details/i18n/lo.ts index 0a13eeacde63..8f598d6e03c6 100644 --- a/packages/gui-extensions/src/details/i18n/lo.ts +++ b/packages/gui-extensions/src/details/i18n/lo.ts @@ -8,16 +8,12 @@ export default { "move.failed": "ຍ້າຍເຊດຊັນບໍ່ສຳເລັດ", server: "ການຂະຫຍາຍ", mcp: "MCP", - "mcp.title": "ເຄື່ອງບໍລິການ MCP ທີ່ຕັ້ງຄ່າແລ້ວ", "mcp.empty": "ບໍ່ມີການຕັ້ງຄ່າເຊີບເວີ MCP", plugins: "ປລັກອິນ", - "plugins.configured": "ປລັກອິນທີ່ຕັ້ງຄ່າແລ້ວ", "plugins.empty": "ບໍ່ມີການຕັ້ງຄ່າປລັກອິນ", skills: "ທັກສະ", - "skills.configured": "ທັກສະທີ່ຕັ້ງຄ່າ", "skills.empty": "ບໍ່ມີການຕັ້ງຄ່າທັກສະ", lsp: "LSP", - "lsp.configured": "LSPs ທີ່ຕັ້ງຄ່າແລ້ວ", "lsp.empty": "ບໍ່ມີການຕັ້ງຄ່າເຊີບເວີ LSP", failed: "ລົ້ມເຫຼວ", retry: "ລອງໃໝ່", diff --git a/packages/gui-extensions/src/details/i18n/lt.ts b/packages/gui-extensions/src/details/i18n/lt.ts index dd962e3e12f6..9a6bfe531da9 100644 --- a/packages/gui-extensions/src/details/i18n/lt.ts +++ b/packages/gui-extensions/src/details/i18n/lt.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Nepavyko perkelti seanso", server: "Pratęsimai", mcp: "MCP", - "mcp.title": "Sukonfigūruoti MCP serveriai", "mcp.empty": "Nekonfigūruota jokių MCP serverių", plugins: "Papildiniai", - "plugins.configured": "Sukonfigūruoti papildiniai", "plugins.empty": "Nėra sukonfigūruotų papildinių", skills: "Įgūdžiai", - "skills.configured": "Sukonfigūruoti įgūdžiai", "skills.empty": "Įgūdžiai nesukonfigūruoti", lsp: "LSP", - "lsp.configured": "Sukonfigūruotas LSPs", "lsp.empty": "Nekonfigūruota jokių LSP serverių", failed: "Nepavyko", retry: "Bandyti dar kartą", diff --git a/packages/gui-extensions/src/details/i18n/lv.ts b/packages/gui-extensions/src/details/i18n/lv.ts index 49a4173efe19..e58456b3c1a1 100644 --- a/packages/gui-extensions/src/details/i18n/lv.ts +++ b/packages/gui-extensions/src/details/i18n/lv.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Neizdevās pārvietot sesiju", server: "Paplašinājumi", mcp: "MCP", - "mcp.title": "Konfigurētie MCP serveri", "mcp.empty": "Nav konfigurēts neviens MCP serveris", plugins: "Spraudņi", - "plugins.configured": "Konfigurēti spraudņi", "plugins.empty": "Nav konfigurēts neviens spraudnis", skills: "Prasmes", - "skills.configured": "Konfigurētas prasmes", "skills.empty": "Nav konfigurētu prasmju", lsp: "LSP", - "lsp.configured": "Konfigurēts LSPs", "lsp.empty": "Nav konfigurēts neviens LSP serveris", failed: "Neizdevās", retry: "Mēģināt vēlreiz", diff --git a/packages/gui-extensions/src/details/i18n/mk.ts b/packages/gui-extensions/src/details/i18n/mk.ts index a445ddc29775..4322cf129abc 100644 --- a/packages/gui-extensions/src/details/i18n/mk.ts +++ b/packages/gui-extensions/src/details/i18n/mk.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Не успеа да се премести сесијата", server: "Проширувања", mcp: "MCP", - "mcp.title": "Конфигурирани MCP сервери", "mcp.empty": "Не се конфигурирани MCP сервери", plugins: "Плагини", - "plugins.configured": "Конфигурирани плагини", "plugins.empty": "Не се конфигурирани приклучоци", skills: "Вештини", - "skills.configured": "Конфигурирани вештини", "skills.empty": "Не се конфигурирани вештини", lsp: "LSP", - "lsp.configured": "Конфигурирани LSP", "lsp.empty": "Не се конфигурирани LSP сервери", failed: "Неуспешно", retry: "Обиди повторно", diff --git a/packages/gui-extensions/src/details/i18n/mn.ts b/packages/gui-extensions/src/details/i18n/mn.ts index 92064051c0fe..a6924101342d 100644 --- a/packages/gui-extensions/src/details/i18n/mn.ts +++ b/packages/gui-extensions/src/details/i18n/mn.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Сессийг шилжүүлж чадсангүй", server: "Өргөтгөлүүд", mcp: "MCP", - "mcp.title": "Тохируулагдсан MCP серверүүд", "mcp.empty": "MCP серверүүд тохируулагдаагүй", plugins: "Плагинууд", - "plugins.configured": "Тохируулагдсан нэмэлт програмууд", "plugins.empty": "Плагинууд тохируулагдаагүй", skills: "Чадварууд", - "skills.configured": "Тохируулагдсан ур чадварууд", "skills.empty": "Шуурхай ур чадвар тохируулагдаагүй", lsp: "LSP", - "lsp.configured": "Тохируулах LSP-ууд", "lsp.empty": "LSP серверүүд тохируулагдаагүй", failed: "Амжилтгүй болсон", retry: "Дахин оролдох", diff --git a/packages/gui-extensions/src/details/i18n/ms.ts b/packages/gui-extensions/src/details/i18n/ms.ts index ba1c27ad6a32..98cc760284e6 100644 --- a/packages/gui-extensions/src/details/i18n/ms.ts +++ b/packages/gui-extensions/src/details/i18n/ms.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Gagal mengalihkan sesi", server: "Lanjutan", mcp: "MCP", - "mcp.title": "Pelayan MCP yang dikonfigurasikan", "mcp.empty": "Tiada pelayan MCP dikonfigurasikan", plugins: "Plugin", - "plugins.configured": "Plugin yang dikonfigurasikan", "plugins.empty": "Tiada plugin dikonfigurasikan", skills: "Kemahiran", - "skills.configured": "Kemahiran yang dikonfigurasikan", "skills.empty": "Tiada kemahiran dikonfigurasikan", lsp: "LSP", - "lsp.configured": "LSP yang dikonfigurasikan", "lsp.empty": "Tiada pelayan LSP dikonfigurasikan", failed: "Gagal", retry: "Cuba semula", diff --git a/packages/gui-extensions/src/details/i18n/my.ts b/packages/gui-extensions/src/details/i18n/my.ts index c6e316f556b8..389e1aeee35a 100644 --- a/packages/gui-extensions/src/details/i18n/my.ts +++ b/packages/gui-extensions/src/details/i18n/my.ts @@ -8,16 +8,12 @@ export default { "move.failed": "စက်ရှင်ကို ရွှေ့ရန် မအောင်မြင်ပါ", server: "အပိုဆောင်းများ", mcp: "MCP", - "mcp.title": "သတ်မှတ်ထားသော MCP ဆာဗာများ", "mcp.empty": "MCP ဆာဗာများ မသတ်မှတ်ထားပါ", plugins: "ပလပ်အင်", - "plugins.configured": "သတ်မှတ်ထားသော ပလပ်အင်များ", "plugins.empty": "ပလပ်အင်များ မသတ်မှတ်ထားပါ", skills: "ကျွမ်းကျင်မှုများ", - "skills.configured": "သတ်မှတ်ထားသော ကျွမ်းကျင်မှုများ", "skills.empty": "ကျွမ်းကျင်မှုများမသတ်မှတ်ထားပါ", lsp: "LSP", - "lsp.configured": "သတ်မှတ်ထားသော LSP များ", "lsp.empty": "LSP ဆာဗာများ မသတ်မှတ်ထားပါ", failed: "မအောင်မြင်ပါ", retry: "ထပ်ကြိုးစားပါ", diff --git a/packages/gui-extensions/src/details/i18n/ne.ts b/packages/gui-extensions/src/details/i18n/ne.ts index 54cf82803b71..a00fac25029d 100644 --- a/packages/gui-extensions/src/details/i18n/ne.ts +++ b/packages/gui-extensions/src/details/i18n/ne.ts @@ -8,16 +8,12 @@ export default { "move.failed": "सत्र सार्न असफल भयो", server: "विस्तारहरू", mcp: "MCP", - "mcp.title": "कन्फिगर गरिएको MCP सर्भरहरू", "mcp.empty": "कुनै MCP सर्भरहरू कन्फिगर गरिएको छैन", plugins: "प्लगइनहरू", - "plugins.configured": "कन्फिगर गरिएका प्लगइनहरू", "plugins.empty": "कुनै प्लगइनहरू कन्फिगर गरिएको छैन", skills: "सीपहरू", - "skills.configured": "कन्फिगर गरिएको कौशल", "skills.empty": "कुनै सीपहरू कन्फिगर गरिएको छैन", lsp: "LSP", - "lsp.configured": "कन्फिगर गरिएको LSPs", "lsp.empty": "कुनै LSP सर्भरहरू कन्फिगर गरिएको छैन", failed: "असफल", retry: "पुन: प्रयास गर्नुहोस्", diff --git a/packages/gui-extensions/src/details/i18n/nl.ts b/packages/gui-extensions/src/details/i18n/nl.ts index d2bf934d185f..d16d42377cf8 100644 --- a/packages/gui-extensions/src/details/i18n/nl.ts +++ b/packages/gui-extensions/src/details/i18n/nl.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Kan sessie niet verplaatsen", server: "Extensies", mcp: "MCP", - "mcp.title": "MCP-servers geconfigureerd", "mcp.empty": "Geen MCP-servers geconfigureerd", plugins: "Plug-ins", - "plugins.configured": "Geconfigureerde plug-ins", "plugins.empty": "Geen plug-ins geconfigureerd", skills: "Vaardigheden", - "skills.configured": "Geconfigureerde vaardigheden", "skills.empty": "Geen vaardigheden geconfigureerd", lsp: "LSP", - "lsp.configured": "LSPs geconfigureerd", "lsp.empty": "Geen LSP-servers geconfigureerd", failed: "Mislukt", retry: "Opnieuw proberen", diff --git a/packages/gui-extensions/src/details/i18n/no.ts b/packages/gui-extensions/src/details/i18n/no.ts index 02bb3317d6e7..5f42a4db411e 100644 --- a/packages/gui-extensions/src/details/i18n/no.ts +++ b/packages/gui-extensions/src/details/i18n/no.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Kunne ikke flytte økten", server: "Utvidelser", mcp: "MCP", - "mcp.title": "Konfigurerte MCP-servere", "mcp.empty": "Ingen MCP-servere er konfigurert", plugins: "Plugins", - "plugins.configured": "Konfigurerte plugins", "plugins.empty": "Ingen plugins er konfigurert", skills: "Ferdigheter", - "skills.configured": "Konfigurerte ferdigheter", "skills.empty": "Ingen ferdigheter konfigurert", lsp: "LSP", - "lsp.configured": "Konfigurert LSPs", "lsp.empty": "Ingen LSP-servere er konfigurert", failed: "Mislyktes", retry: "Prøv på nytt", diff --git a/packages/gui-extensions/src/details/i18n/pa.ts b/packages/gui-extensions/src/details/i18n/pa.ts index 8ba03b1596d0..1bc73b8569a8 100644 --- a/packages/gui-extensions/src/details/i18n/pa.ts +++ b/packages/gui-extensions/src/details/i18n/pa.ts @@ -8,16 +8,12 @@ export default { "move.failed": "سیشن لے جان چ ناکامی ہوئی", server: "ایکسٹینشنز", mcp: "MCP", - "mcp.title": "ترتیب شدہ MCP سرورز", "mcp.empty": "کوئی MCP سرورز کنفیگر نئیں ہوئے۔", plugins: "پلگ انز", - "plugins.configured": "کنفیگرڈ پلگ انز", "plugins.empty": "کوئی پلگ ان کنفیگر نئیں اے۔", skills: "ہنر", - "skills.configured": "کنفیگرڈ ہنر", "skills.empty": "کوئی ہنر ترتیب نئیں دیا گیا اے۔", lsp: "LSP", - "lsp.configured": "ترتیب شدہ LSPs", "lsp.empty": "کوئی LSP سرورز کنفیگر نئیں ہوئے۔", failed: "ناکام", retry: "دوبارہ کوشش کرو۔", diff --git a/packages/gui-extensions/src/details/i18n/pl.ts b/packages/gui-extensions/src/details/i18n/pl.ts index 169316f726f4..8732a2c8d79b 100644 --- a/packages/gui-extensions/src/details/i18n/pl.ts +++ b/packages/gui-extensions/src/details/i18n/pl.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Nie udało się przenieść sesji", server: "Rozszerzenia", mcp: "MCP", - "mcp.title": "Skonfigurowane serwery MCP", "mcp.empty": "Brak skonfigurowanych serwerów MCP", plugins: "Wtyczki", - "plugins.configured": "Skonfigurowane wtyczki", "plugins.empty": "Nie skonfigurowano żadnych wtyczek", skills: "Umiejętności", - "skills.configured": "Skonfigurowane umiejętności", "skills.empty": "Nie skonfigurowano żadnych umiejętności", lsp: "LSP", - "lsp.configured": "Skonfigurowane LSP", "lsp.empty": "Brak skonfigurowanych serwerów LSP", failed: "Nie powiodło się", retry: "Spróbuj ponownie", diff --git a/packages/gui-extensions/src/details/i18n/ro.ts b/packages/gui-extensions/src/details/i18n/ro.ts index e89849d0e4b2..bb72811f3d5e 100644 --- a/packages/gui-extensions/src/details/i18n/ro.ts +++ b/packages/gui-extensions/src/details/i18n/ro.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Mutarea sesiunii a eșuat", server: "Extensii", mcp: "MCP", - "mcp.title": "Servere MCP configurate", "mcp.empty": "Nu au fost configurate servere MCP", plugins: "Pluginuri", - "plugins.configured": "Pluginuri configurate", "plugins.empty": "Nu au fost configurate pluginuri", skills: "Abilități", - "skills.configured": "Abilități configurate", "skills.empty": "Nicio abilitate configurată", lsp: "LSP", - "lsp.configured": "LSPs configurat", "lsp.empty": "Nu au fost configurate servere LSP", failed: "A eșuat", retry: "Reîncercați", diff --git a/packages/gui-extensions/src/details/i18n/ru.ts b/packages/gui-extensions/src/details/i18n/ru.ts index f81b47589f04..444dbdee85db 100644 --- a/packages/gui-extensions/src/details/i18n/ru.ts +++ b/packages/gui-extensions/src/details/i18n/ru.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Не удалось переместить сеанс.", server: "Расширения", mcp: "MCP", - "mcp.title": "Настроенные серверы MCP", "mcp.empty": "MCP-серверы не настроены", plugins: "Плагины", - "plugins.configured": "Настроенные плагины", "plugins.empty": "Плагины не настроены", skills: "Навыки", - "skills.configured": "Настроенные навыки", "skills.empty": "Навыки не настроены", lsp: "LSP", - "lsp.configured": "Настроенные LSP", "lsp.empty": "LSP-серверы не настроены", failed: "Не удалось", retry: "Повторить попытку", diff --git a/packages/gui-extensions/src/details/i18n/si.ts b/packages/gui-extensions/src/details/i18n/si.ts index e0ccbb02009a..f54da5af9f3e 100644 --- a/packages/gui-extensions/src/details/i18n/si.ts +++ b/packages/gui-extensions/src/details/i18n/si.ts @@ -8,16 +8,12 @@ export default { "move.failed": "සැසිය ගෙන යාමට අසමත් විය", server: "දිගු", mcp: "MCP", - "mcp.title": "MCP සේවාදායක වින්‍යාස කර ඇත", "mcp.empty": "MCP සේවාදායක වින්‍යාස කර නැත", plugins: "ප්ලගීන", - "plugins.configured": "වින්‍යාසගත ප්ලගීන", "plugins.empty": "ප්ලගින වින්‍යාස කර නැත", skills: "කුසලතා", - "skills.configured": "වින්යාසගත කුසලතා", "skills.empty": "කුසලතා වින්‍යාස කර නැත", lsp: "LSP", - "lsp.configured": "වින්‍යාසගත LSPs", "lsp.empty": "LSP සේවාදායක වින්‍යාස කර නැත", failed: "අසාර්ථකයි", retry: "නැවත උත්සාහ කරන්න", diff --git a/packages/gui-extensions/src/details/i18n/sk.ts b/packages/gui-extensions/src/details/i18n/sk.ts index 172915302bf4..af02dd81164b 100644 --- a/packages/gui-extensions/src/details/i18n/sk.ts +++ b/packages/gui-extensions/src/details/i18n/sk.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Presun relácie zlyhal", server: "Rozšírenia", mcp: "MCP", - "mcp.title": "Nakonfigurované MCP servery", "mcp.empty": "Nie sú nakonfigurované žiadne MCP servery", plugins: "Pluginy", - "plugins.configured": "Nakonfigurované pluginy", "plugins.empty": "Nie sú nakonfigurované žiadne pluginy", skills: "Schopnosti", - "skills.configured": "Nakonfigurované schopnosti", "skills.empty": "Nie sú nakonfigurované žiadne schopnosti", lsp: "LSP", - "lsp.configured": "Nakonfigurované LSP", "lsp.empty": "Nie sú nakonfigurované žiadne LSP servery", failed: "Zlyhalo", retry: "Skúsiť znova", diff --git a/packages/gui-extensions/src/details/i18n/sl.ts b/packages/gui-extensions/src/details/i18n/sl.ts index 53647244dd4a..133caa05388e 100644 --- a/packages/gui-extensions/src/details/i18n/sl.ts +++ b/packages/gui-extensions/src/details/i18n/sl.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Seje ni bilo mogoče premakniti", server: "Razširitve", mcp: "MCP", - "mcp.title": "Konfigurirani MCP strežniki", "mcp.empty": "Brez konfiguriranih MCP strežnikov", plugins: "Vtičniki", - "plugins.configured": "Konfigurirani vtičniki", "plugins.empty": "Brez konfiguriranih vtičnikov", skills: "Veščine", - "skills.configured": "Konfigurirane veščine", "skills.empty": "Brez konfiguriranih veščin", lsp: "LSP", - "lsp.configured": "Konfigurirani LSP-ji", "lsp.empty": "Brez konfiguriranih LSP strežnikov", failed: "Ni uspelo", retry: "Poskusi znova", diff --git a/packages/gui-extensions/src/details/i18n/sq.ts b/packages/gui-extensions/src/details/i18n/sq.ts index 53a3c6ff4fb5..09bd1797f3a3 100644 --- a/packages/gui-extensions/src/details/i18n/sq.ts +++ b/packages/gui-extensions/src/details/i18n/sq.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Zhvendosja e sesionit dështoi", server: "Zgjerime", mcp: "MCP", - "mcp.title": "Serverët e konfiguruar MCP", "mcp.empty": "Nuk ka serverë MCP të konfiguruar", plugins: "Plugins", - "plugins.configured": "Shtojcat e konfiguruara", "plugins.empty": "Asnjë shtojcë nuk është konfiguruar", skills: "Aftësitë", - "skills.configured": "Aftësitë e konfiguruara", "skills.empty": "Nuk ka aftësi të konfiguruara", lsp: "LSP", - "lsp.configured": "LSPs është konfiguruar", "lsp.empty": "Nuk ka serverë LSP të konfiguruar", failed: "Dështoi", retry: "Riprovo", diff --git a/packages/gui-extensions/src/details/i18n/sr.ts b/packages/gui-extensions/src/details/i18n/sr.ts index 073408d91acd..2e849e1da904 100644 --- a/packages/gui-extensions/src/details/i18n/sr.ts +++ b/packages/gui-extensions/src/details/i18n/sr.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Премештање сесије није успело", server: "Екстензије", mcp: "MCP", - "mcp.title": "Конфигурисани MCP сервери", "mcp.empty": "Нема конфигурисаних MCP сервера", plugins: "Додаци", - "plugins.configured": "Конфигурисани додаци", "plugins.empty": "Нема конфигурисаних додатака", skills: "Вештине", - "skills.configured": "Конфигурисане вештине", "skills.empty": "Нема конфигурисаних вештина", lsp: "LSP", - "lsp.configured": "Конфигурисани LSP-ови", "lsp.empty": "Нема конфигурисаних LSP сервера", failed: "Неуспешно", retry: "Покушај поново", diff --git a/packages/gui-extensions/src/details/i18n/sv.ts b/packages/gui-extensions/src/details/i18n/sv.ts index d8f319b0bd29..94690e4d8847 100644 --- a/packages/gui-extensions/src/details/i18n/sv.ts +++ b/packages/gui-extensions/src/details/i18n/sv.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Det gick inte att flytta sessionen", server: "Tillägg", mcp: "MCP", - "mcp.title": "Konfigurerade MCP-servrar", "mcp.empty": "Inga MCP-servrar har konfigurerats", plugins: "Plugins", - "plugins.configured": "Konfigurerade plugins", "plugins.empty": "Inga plugins konfigurerade", skills: "Färdigheter", - "skills.configured": "Konfigurerade färdigheter", "skills.empty": "Inga färdigheter konfigurerade", lsp: "LSP", - "lsp.configured": "Konfigurerad LSPs", "lsp.empty": "Inga LSP-servrar har konfigurerats", failed: "Misslyckades", retry: "Försök igen", diff --git a/packages/gui-extensions/src/details/i18n/tg.ts b/packages/gui-extensions/src/details/i18n/tg.ts index 86ae77472029..920fe776525c 100644 --- a/packages/gui-extensions/src/details/i18n/tg.ts +++ b/packages/gui-extensions/src/details/i18n/tg.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Гузаронидани сессия муяссар нашуд", server: "Васеъшавӣ", mcp: "MCP", - "mcp.title": "Серверҳои танзимшудаи MCP", "mcp.empty": "Ягон сервери MCP танзим карда нашудааст", plugins: "Плагинҳо", - "plugins.configured": "Плагинҳои танзимшуда", "plugins.empty": "Ягон плагинҳо танзим карда нашудаанд", skills: "Маҳорат", - "skills.configured": "Малакаҳои танзимшуда", "skills.empty": "Ягон малака танзим карда нашудааст", lsp: "LSP", - "lsp.configured": "LSP-ҳои танзимшуда", "lsp.empty": "Ҳеҷ сервери LSP танзим нашудааст", failed: "Муваффақ шуд", retry: "Аз нав кӯшиш кунед", diff --git a/packages/gui-extensions/src/details/i18n/th.ts b/packages/gui-extensions/src/details/i18n/th.ts index 9ad254213eb6..c60d766d1211 100644 --- a/packages/gui-extensions/src/details/i18n/th.ts +++ b/packages/gui-extensions/src/details/i18n/th.ts @@ -8,16 +8,12 @@ export default { "move.failed": "ไม่สามารถย้ายเซสชัน", server: "ส่วนขยาย", mcp: "MCP", - "mcp.title": "เซิร์ฟเวอร์ MCP ที่กำหนดค่าแล้ว", "mcp.empty": "ยังไม่ได้กำหนดค่าเซิร์ฟเวอร์ MCP", plugins: "ปลั๊กอิน", - "plugins.configured": "ปลั๊กอินที่กำหนดค่าแล้ว", "plugins.empty": "ยังไม่ได้กำหนดค่าปลั๊กอิน", skills: "ทักษะ", - "skills.configured": "ทักษะที่กำหนดค่าแล้ว", "skills.empty": "ยังไม่ได้ตั้งค่าทักษะ", lsp: "LSP", - "lsp.configured": "LSP ที่ตั้งค่าแล้ว", "lsp.empty": "ยังไม่ได้ตั้งค่าเซิร์ฟเวอร์ LSP", failed: "ล้มเหลว", retry: "ลองใหม่", diff --git a/packages/gui-extensions/src/details/i18n/tk.ts b/packages/gui-extensions/src/details/i18n/tk.ts index d98217aaf10a..a1044d309214 100644 --- a/packages/gui-extensions/src/details/i18n/tk.ts +++ b/packages/gui-extensions/src/details/i18n/tk.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Sessiýany geçirip bolmady", server: "Giňeldişler", mcp: "MCP", - "mcp.title": "Sazlanan MCP serwerleri", "mcp.empty": "Hiç hili MCP serweri düzülmedi", plugins: "Pluginler", - "plugins.configured": "Sazlanan pluginler", "plugins.empty": "Hiç hili plugin düzülmedi", skills: "Ussatlyk", - "skills.configured": "Sazlanan başarnyklar", "skills.empty": "Hiç hili hünär düzülmedi", lsp: "LSP", - "lsp.configured": "Sazlanan LSP-ler", "lsp.empty": "LSP serwerleri düzülmedi", failed: "Şowsuz", retry: "Gaýtadan synanyşyň", diff --git a/packages/gui-extensions/src/details/i18n/tr.ts b/packages/gui-extensions/src/details/i18n/tr.ts index eb2c111b1500..79fd4f534304 100644 --- a/packages/gui-extensions/src/details/i18n/tr.ts +++ b/packages/gui-extensions/src/details/i18n/tr.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Oturum çalışma alanına taşınamadı", server: "Uzantılar", mcp: "MCP", - "mcp.title": "Yapılandırılmış MCP sunucuları", "mcp.empty": "Hiçbir MCP sunucusu yapılandırılmadı", plugins: "Eklentiler", - "plugins.configured": "Yapılandırılmış eklentiler", "plugins.empty": "Hiçbir eklenti yapılandırılmadı", skills: "Beceriler", - "skills.configured": "Yapılandırılmış beceriler", "skills.empty": "Hiçbir beceri yapılandırılmadı", lsp: "LSP", - "lsp.configured": "Yapılandırılmış LSPs", "lsp.empty": "Hiçbir LSP sunucusu yapılandırılmadı", failed: "Başarısız", retry: "Yeniden dene", diff --git a/packages/gui-extensions/src/details/i18n/uk.ts b/packages/gui-extensions/src/details/i18n/uk.ts index 0468f53f97e9..05985ca57223 100644 --- a/packages/gui-extensions/src/details/i18n/uk.ts +++ b/packages/gui-extensions/src/details/i18n/uk.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Не вдалося перемістити сеанс", server: "Розширення", mcp: "MCP", - "mcp.title": "Налаштовані сервери MCP", "mcp.empty": "Не налаштовано серверів MCP", plugins: "Плагіни", - "plugins.configured": "Налаштовані плагіни", "plugins.empty": "Не налаштовано плагінів", skills: "Навички", - "skills.configured": "Налаштовані навички", "skills.empty": "Не налаштовано навичок", lsp: "LSP", - "lsp.configured": "Налаштовані LSP", "lsp.empty": "Не налаштовано серверів LSP", failed: "Не вдалося", retry: "Повторити", diff --git a/packages/gui-extensions/src/details/i18n/ur.ts b/packages/gui-extensions/src/details/i18n/ur.ts index 93b64ceab3da..150eaad2ee7e 100644 --- a/packages/gui-extensions/src/details/i18n/ur.ts +++ b/packages/gui-extensions/src/details/i18n/ur.ts @@ -8,16 +8,12 @@ export default { "move.failed": "سیشن منتقل کرنے میں ناکام", server: "ایکسٹینشنز", mcp: "MCP", - "mcp.title": "ترتیب شدہ MCP سرورز", "mcp.empty": "کوئی MCP سرورز کنفیگر نہیں ہوئے۔", plugins: "پلگ انز", - "plugins.configured": "کنفیگرڈ پلگ انز", "plugins.empty": "کوئی پلگ ان کنفیگر نہیں ہے۔", skills: "ہنر", - "skills.configured": "کنفیگرڈ ہنر", "skills.empty": "کوئی ہنر ترتیب نہیں دیا گیا ہے۔", lsp: "LSP", - "lsp.configured": "ترتیب شدہ LSPs", "lsp.empty": "کوئی LSP سرورز کنفیگر نہیں ہوئے۔", failed: "ناکام", retry: "دوبارہ کوشش کریں۔", diff --git a/packages/gui-extensions/src/details/i18n/uz.ts b/packages/gui-extensions/src/details/i18n/uz.ts index 8aabeafe2394..bec231b9742a 100644 --- a/packages/gui-extensions/src/details/i18n/uz.ts +++ b/packages/gui-extensions/src/details/i18n/uz.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Sessiyani ko‘chirib bo‘lmadi", server: "Kengaytmalar", mcp: "MCP", - "mcp.title": "Sozlangan MCP serverlari", "mcp.empty": "Hech qanday MCP serveri sozlanmagan", plugins: "Plaginlar", - "plugins.configured": "Sozlangan plaginlar", "plugins.empty": "Hech qanday plagin sozlanmagan", skills: "Ko'nikmalar", - "skills.configured": "Sozlangan ko'nikmalar", "skills.empty": "Hech qanday mahorat sozlanmagan", lsp: "LSP", - "lsp.configured": "Sozlangan LSPlar", "lsp.empty": "Hech qanday LSP serveri sozlanmagan", failed: "Muvaffaqiyatsiz", retry: "Qayta urinish", diff --git a/packages/gui-extensions/src/details/i18n/vi.ts b/packages/gui-extensions/src/details/i18n/vi.ts index 2d82907ea7dd..2f410272e6e1 100644 --- a/packages/gui-extensions/src/details/i18n/vi.ts +++ b/packages/gui-extensions/src/details/i18n/vi.ts @@ -8,16 +8,12 @@ export default { "move.failed": "Không chuyển được phiên", server: "Tiện ích mở rộng", mcp: "MCP", - "mcp.title": "Máy chủ MCP đã cấu hình", "mcp.empty": "Chưa cấu hình máy chủ MCP nào", plugins: "Tiện ích", - "plugins.configured": "Tiện ích đã cấu hình", "plugins.empty": "Chưa cấu hình tiện ích nào", skills: "Kỹ năng", - "skills.configured": "Kỹ năng đã cấu hình", "skills.empty": "Chưa cấu hình kỹ năng nào", lsp: "LSP", - "lsp.configured": "LSP đã cấu hình", "lsp.empty": "Chưa cấu hình máy chủ LSP nào", failed: "Thất bại", retry: "Thử lại", diff --git a/packages/gui-extensions/src/details/i18n/zh.ts b/packages/gui-extensions/src/details/i18n/zh.ts index d71b14af21b6..211859ffd5a4 100644 --- a/packages/gui-extensions/src/details/i18n/zh.ts +++ b/packages/gui-extensions/src/details/i18n/zh.ts @@ -8,16 +8,12 @@ export default { "move.failed": "移动会话失败", server: "扩展", mcp: "MCP", - "mcp.title": "已配置的 MCP 服务器", "mcp.empty": "未配置 MCP 服务器", plugins: "插件", - "plugins.configured": "已配置的插件", "plugins.empty": "未配置插件", skills: "技能", - "skills.configured": "已配置的技能", "skills.empty": "未配置技能", lsp: "LSP", - "lsp.configured": "已配置的 LSP", "lsp.empty": "未配置 LSP 服务器", failed: "失败", retry: "重试", diff --git a/packages/gui-extensions/src/details/i18n/zht.ts b/packages/gui-extensions/src/details/i18n/zht.ts index aa35b9b59cfb..8645f0f5307f 100644 --- a/packages/gui-extensions/src/details/i18n/zht.ts +++ b/packages/gui-extensions/src/details/i18n/zht.ts @@ -8,16 +8,12 @@ export default { "move.failed": "移動工作階段失敗", server: "擴充功能", mcp: "MCP", - "mcp.title": "已設定的 MCP 伺服器", "mcp.empty": "尚未設定任何 MCP 伺服器", plugins: "外掛程式", - "plugins.configured": "已設定的外掛程式", "plugins.empty": "尚未設定任何外掛程式", skills: "技能", - "skills.configured": "已設定的技能", "skills.empty": "未設定技能", lsp: "LSP", - "lsp.configured": "已設定的 LSP", "lsp.empty": "未設定 LSP 伺服器", failed: "失敗", retry: "重試", diff --git a/packages/gui-extensions/src/details/popover.tsx b/packages/gui-extensions/src/details/popover.tsx index d435dc0368e6..6e8ac69d92ff 100644 --- a/packages/gui-extensions/src/details/popover.tsx +++ b/packages/gui-extensions/src/details/popover.tsx @@ -41,7 +41,18 @@ function DetailsPopover( // Cached timelines remain mounted; only the visible details own the command. It leaves with this component. // A shortcut opens the popover without clicking its trigger. let trigger: HTMLButtonElement | undefined - const self = { trigger: () => trigger, toggle: () => props.onOpenChange(!props.open) } + // How the popover last opened: a pointer open focuses the popover itself, so its first row shows no focus ring + // beside the row the pointer opens. + const opening = { pointer: false } + + const self = { + trigger: () => trigger, + toggle: () => { + opening.pointer = false + props.onOpenChange(!props.open) + }, + } + props.menus.add(self) onCleanup(() => props.menus.delete(self)) @@ -84,14 +95,29 @@ function DetailsPopover( state={props.open ? "pressed" : undefined} aria-label={ctx.t("title")} aria-expanded={props.open} + onPointerDown={() => (opening.pointer = true)} + onKeyDown={() => (opening.pointer = false)} /> + {/* Kobalte positions the content relatively, so it places the Extensions flyout. Only the inner box scrolls, + so the flyout beside it is not clipped. */} { + if (!opening.pointer) return + event.preventDefault() + + const content = event.currentTarget + + // Deferred like Kobalte's own autofocus, which runs once the content is in the document. + if (content instanceof HTMLElement) setTimeout(() => content.focus({ preventScroll: true })) + }} > - {props.children} +
{props.children}
diff --git a/packages/gui-extensions/src/details/server-panel.tsx b/packages/gui-extensions/src/details/server-panel.tsx index 55a5cb3e1d77..2881cd0d7bd9 100644 --- a/packages/gui-extensions/src/details/server-panel.tsx +++ b/packages/gui-extensions/src/details/server-panel.tsx @@ -1,4 +1,3 @@ -import { Popover } from "@kobalte/core/popover" import type { PluginInfo } from "@opencode/client" import { Button } from "@opencode/ui/button" import { Dialog, DialogBody, DialogHeader, DialogTitleGroup } from "@opencode/ui/dialog" @@ -10,7 +9,20 @@ import { Tooltip } from "@opencode/ui/tooltip" import { TruncatedText } from "@opencode/ui/truncated-text" import { getDirectory } from "@opencode/util/path" import { useMutation } from "@tanstack/solid-query" -import { children, createMemo, createResource, createSignal, For, Index, on, onCleanup, Show, type JSX } from "solid-js" +import { + children, + createMemo, + createResource, + createSignal, + createUniqueId, + For, + Index, + on, + onCleanup, + Show, + type Component, + type JSX, +} from "solid-js" import { createStore } from "solid-js/store" import { createKeyed, useDrawer, useExtension, type MountedSession } from "../sdk" import { configuredLsps } from "./configured-lsp" @@ -24,73 +36,446 @@ const services = [ type Service = (typeof services)[number]["type"] -type ServiceMenuProps = { +type ServiceProps = { session: MountedSession - service: (typeof services)[number] directory: string shown: boolean - open: boolean - mobile?: boolean - onOpenChange: (open: boolean) => void } -export function SessionServerPanel(props: { - session: MountedSession - directory: string - shown: boolean - mobile?: boolean -}) { +/** One service's list: its load state, what its row summarises, and the list itself. */ +type ServiceSource = { + readonly loading: boolean + readonly ready: boolean + readonly error: boolean + readonly count: number + readonly failed: boolean + retry: () => void + Body: Component +} + +// How long a pointer may rest inside the safe triangle before the row under it takes over. +const AIM_TIMEOUT = 100 + +export function SessionServerPanel(props: ServiceProps & { mobile?: boolean }) { const ctx = useExtension() const name = () => (ctx.servers.list().length < 2 ? ctx.t("server") : props.session.server.name) - // A new scope whenever the location or visibility changes closes the service popover. - const scope = createMemo(on([() => props.directory, () => props.shown], () => ({}))) - const [submenu, setSubmenu] = createSignal<{ readonly scope: object; readonly service: Service }>() - const open = () => { - const current = submenu() + return ( + + + + } + > + {(directory) => ( + + )} + + ) +} + +function ServerHeading(props: { name: string }) { + return ( +

+ + {props.name} + +

+ ) +} + +function ServiceMenu(props: ServiceProps & { mobile?: boolean; name: string }) { + const ctx = useExtension() + const drawer = useDrawer() + const id = createUniqueId() + + // A new scope whenever visibility changes closes the flyout. + const scope = createMemo( + on( + () => props.shown, + () => ({}), + ), + ) + + // A signal, not a store field: a store would wrap the scope object and never match it. + const [open, setOpen] = createSignal<{ readonly scope: object; readonly service: Service }>() + + const [menu, setMenu] = createStore<{ + // The pane on show, kept through the flyout's exit so it fades out with its list. + pane: Service | undefined + // Where the trigger row sits in the flyout's containing block, and the room around it in the viewport. + anchor: { row: number; above: number; below: number } + heights: Partial> + overflow: Partial> + }>({ pane: undefined, anchor: { row: 0, above: 0, below: 0 }, heights: {}, overflow: {} }) + + const sources: Record = { + mcp: useMcp(props, () => close()), + plugins: useCatalog(props, "plugins"), + skills: useCatalog(props, "skills"), + lsp: useLsp(props), + } + + const active = () => { + const current = open() return current?.scope === scope() ? current.service : undefined } + const height = () => (menu.pane ? (menu.heights[menu.pane] ?? 0) : 0) + 4 + // Beside its row, kept inside the viewport. + const offset = () => Math.max(-menu.anchor.above, Math.min(menu.anchor.row - 2, menu.anchor.below - height())) + + const rows: Partial> = {} + const panes: Partial> = {} + let flyout: HTMLDivElement | undefined + let surface: HTMLDivElement | undefined + // The flyout's containing block in the viewport, measured when a list opens and on scroll or resize, so pointer + // moves never read layout. + const bounds = { left: 0, right: 0, top: 0 } + // Each list refreshes the first time it shows after the flyout opens. + const refreshed = new Set() + // The two latest pointer positions at least 2px apart, which give the pointer's heading. + const trail = { x: 0, y: 0, px: 0, py: 0 } + let pending: ReturnType | undefined + onCleanup(() => clearTimeout(pending)) + + // Places the flyout beside a row: where the row sits in the containing block, and the room around it. + const measure = (service: Service) => { + const row = rows[service] + const box = flyout?.offsetParent + + if (!row || !box) return false + const base = box.getBoundingClientRect() + + bounds.left = base.left + bounds.right = base.right + bounds.top = base.top + setMenu("anchor", { + row: row.getBoundingClientRect().top - base.top, + above: base.top - 16, + below: window.innerHeight - 16 - base.top, + }) + + return true + } + + const activate = (service: Service, focus = false) => { + clearTimeout(pending) + + if (!flyout || !surface) return + const opening = active() === undefined + + if (!refreshed.has(service)) { + refreshed.add(service) + + if (!sources[service].loading) sources[service].retry() + } + + // The first open lands beside its row at its list's height; later switches glide and resize. + if (opening) { + flyout.style.transition = "none" + surface.style.transition = "none" + } + + if (!measure(service)) return + setOpen({ scope: scope(), service }) + setMenu("pane", service) + + if (opening) { + void flyout.offsetHeight + flyout.style.removeProperty("transition") + surface.style.removeProperty("transition") + } + + if (focus) { + const pane = panes[service] + + if (pane) (focusables(pane)[0] ?? pane).focus() + } + } + + const close = (focus = false) => { + clearTimeout(pending) + const service = active() + + // Focus returns to the row before the list goes inert, so it never falls to the document. + if (focus && service) rows[service]?.focus() + refreshed.clear() + setOpen(undefined) + } + + // Moving between rows switches at once, except while the pointer heads for the open flyout across other rows. + const hover = (event: PointerEvent) => { + if (event.pointerType === "touch") return + + if (Math.hypot(event.clientX - trail.x, event.clientY - trail.y) >= 2) { + trail.px = trail.x + trail.py = trail.y + trail.x = event.clientX + trail.y = event.clientY + } + + // A closed flyout opens when the pointer enters a row, so a pointer resting where Escape closed it stays put. + if (!active()) return + const target = event.target + + const service = + target instanceof Node ? services.find((item) => rows[item.type]?.contains(target))?.type : undefined + + if (!service || service === active()) return clearTimeout(pending) + + if (!aiming(event)) return activate(service) + clearTimeout(pending) + pending = setTimeout(() => activate(service), AIM_TIMEOUT) + } + + // Whether the pointer's last step points into the open flyout: inside the triangle from its previous position to + // the flyout's near edge. + const aiming = (event: PointerEvent) => { + if (!active()) return false + // The flyout sits against the containing block's inline start, where it is heading rather than where it is. + const edge = ctx.locale.direction() === "rtl" ? bounds.right : bounds.left + const top = bounds.top + offset() + + return insideTriangle( + { x: event.clientX, y: event.clientY }, + { x: trail.px, y: trail.py }, + { x: edge, y: top - 8 }, + { x: edge, y: top + height() + 8 }, + ) + } + + const keydown = (event: KeyboardEvent) => { + const target = event.target + + if (!(target instanceof HTMLElement)) return + const rtl = ctx.locale.direction() === "rtl" + const into = rtl ? "ArrowLeft" : "ArrowRight" + const back = rtl ? "ArrowRight" : "ArrowLeft" + const row = services.findIndex((item) => rows[item.type] === target) + + if (row !== -1) { + if (event.key === into) { + event.preventDefault() + activate(services[row].type, true) + } + + if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return + event.preventDefault() + const next = services[(row + (event.key === "ArrowDown" ? 1 : services.length - 1)) % services.length].type + rows[next]?.focus() + + if (active()) activate(next) + + return + } + + const pane = menu.pane ? panes[menu.pane] : undefined + + if (!pane?.contains(target)) return + + if (event.key === back) { + event.preventDefault() + close(true) + + return + } + + if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return + event.preventDefault() + const items = focusables(pane) + const index = items.findIndex((item) => item === target || item.contains(target)) + const step = event.key === "ArrowDown" ? 1 : -1 + items[index === -1 ? 0 : (index + step + items.length) % items.length]?.focus() + } + + // While a list is open: Escape closes only the flyout, before the details popover's own Escape handling sees it, + // and scrolling or resizing measures the flyout's place again. + createKeyed( + () => active() !== undefined, + () => { + const remeasure = () => { + const service = active() + + if (service) measure(service) + } + + const escape = (event: KeyboardEvent) => { + const focused = document.activeElement + + if (event.key !== "Escape" || !focused || !flyout?.offsetParent?.contains(focused)) return + event.preventDefault() + event.stopPropagation() + close(!!flyout?.contains(focused)) + } + + document.addEventListener("keydown", escape, true) + document.addEventListener("scroll", remeasure, { capture: true, passive: true }) + window.addEventListener("resize", remeasure) + onCleanup(() => { + document.removeEventListener("keydown", escape, true) + document.removeEventListener("scroll", remeasure, true) + window.removeEventListener("resize", remeasure) + }) + }, + ) + + // Measures each list, so the flyout clips to the one on show and knows when it scrolls. + const observer = new ResizeObserver((entries) => + entries.forEach((entry) => { + const service = services.find((item) => panes[item.type]?.contains(entry.target))?.type + const pane = service ? panes[service] : undefined + + if (!service || !pane) return + setMenu("heights", service, pane.offsetHeight) + setMenu("overflow", service, pane.scrollHeight > pane.clientHeight + 1) + }), + ) + + onCleanup(() => observer.disconnect()) + + if (props.mobile && drawer) { + return ( +
+ +
+ + {(service) => { + const source = sources[service.type] + // In a narrow-screen drawer the service list replaces the drawer's view. + + const content = children(() => ( +
+ + + +
+ )) + + return ( + + ) + }} +
+
+
+ ) + } + return ( -
-

- - {name()} - -

- - {(directory) => ( -
+ <> +
+ +
clearTimeout(pending)}> + + {(service) => { + const source = sources[service.type] + + return ( + + ) + }} + +
+
+
clearTimeout(pending)} + > +
+
- {(service) => ( - setSubmenu(value ? { scope: scope(), service: service.type } : undefined)} - /> - )} + {(service) => { + const source = sources[service.type] + + return ( +
{ + panes[service.type] = element + observer.observe(element) + }} + id={`${id}-${service.type}`} + class="session-service-pane" + role="group" + tabIndex={-1} + aria-label={ctx.t(service.label)} + aria-busy={source.loading} + data-service={service.type} + data-active={menu.pane === service.type || undefined} + data-overflow={menu.overflow[service.type] || undefined} + inert={menu.pane !== service.type || undefined} + > +
observer.observe(element)} class="session-service-pane-content"> + + + +
+
+ ) + }}
- )} - -
+ + + ) } -function ServiceMenu(props: ServiceMenuProps) { - if (props.service.type === "mcp") return - - if (props.service.type === "lsp") return - - return -} - -function LspMenu(props: ServiceMenuProps) { +function useLsp(props: ServiceProps): ServiceSource { const ctx = useExtension() const data = () => props.session.server.data @@ -116,28 +501,32 @@ function LspMenu(props: ServiceMenuProps) { ), ) - return ( - + return { + get loading() { + return load.loading + }, + get ready() { + return data().location.config.list({ directory: props.directory }) !== undefined + }, + get error() { + return !!load.error + }, + get count() { + return names().length + }, + failed: false, + retry: () => void refetch(), + Body: () => ( } > -

{ctx.t("lsp.configured")}

{(name) => (
- - {name} - +
)}
@@ -145,11 +534,11 @@ function LspMenu(props: ServiceMenuProps) {
-
- ) + ), + } } -function McpMenu(props: ServiceMenuProps) { +function useMcp(props: ServiceProps, close: () => void): ServiceSource { const ctx = useExtension() const data = () => props.session.server.data @@ -176,31 +565,45 @@ function McpMenu(props: ServiceMenuProps) { )) - return ( - + return { + get loading() { + return load.loading + }, + get ready() { + return data().location.mcp.server.list({ directory: props.directory }) !== undefined + }, + get error() { + return !!load.error + }, + get count() { + return servers().length + }, + get failed() { + return servers().some((server) => server.status.status === "failed") + }, + retry: () => void refetch(), + Body: () => ( } > -

{ctx.t("mcp.title")}

{(server) => { const enabled = () => server().status.status !== "disabled" const pending = () => toggle.isPending || server().status.status === "pending" - // Status stays beside its control rather than competing with a long server name. - const failed = () => server().status.status === "failed" + + const failure = () => { + const status = server().status + + return status.status === "failed" ? status.error : undefined + } + const signIn = () => server().status.status === "needs_auth" && !!server().integrationID const busy = () => toggle.isPending && toggle.variables?.name === server().name + // The dot shows the status; the words stay for assistive technology. const label = () => { const status = server().status.status @@ -208,6 +611,8 @@ function McpMenu(props: ServiceMenuProps) { if (status === "needs_auth") return ctx.t("needsAuth") + if (status === "failed") return ctx.t("failed") + return ctx.t(status === "connected" ? "mcp.connected" : "mcp.disabled") } @@ -217,50 +622,52 @@ function McpMenu(props: ServiceMenuProps) { } const follow = () => { - if (failed()) return showError(server().name) + if (failure()) return showError(server().name) change(server().status.status === "needs_auth" || !enabled()) } return ( -
+
- event.stopPropagation()}> {server().name} - } > - - {ctx.t("failed")} - @@ -274,7 +681,7 @@ function McpMenu(props: ServiceMenuProps) { type="button" class="session-service-config" onClick={() => { - props.onOpenChange(false) + close() ctx.layout.command("mcp.toggle") }} > @@ -283,11 +690,11 @@ function McpMenu(props: ServiceMenuProps) {
- - ) + ), + } } -function ServiceCatalog(props: ServiceMenuProps) { +function useCatalog(props: ServiceProps, service: "plugins" | "skills"): ServiceSource { const ctx = useExtension() const data = () => props.session.server.data @@ -296,7 +703,7 @@ function ServiceCatalog(props: ServiceMenuProps) { async (current) => { const directory = props.directory - if (props.service.type === "plugins") { + if (service === "plugins") { const result = await props.session.server.client.plugin.list({ location: { directory } }) return result.data @@ -319,13 +726,13 @@ function ServiceCatalog(props: ServiceMenuProps) { const list = createMemo(() => { const entries = - props.service.type === "plugins" + service === "plugins" ? loaded() ? (items.latest ?? []) : [] : (data().location.skill.list({ directory: props.directory }) ?? []).map((skill) => ({ name: skill.name, - status: "active", + status: undefined, error: undefined, })) @@ -334,165 +741,108 @@ function ServiceCatalog(props: ServiceMenuProps) { createKeyed(data, (current) => onCleanup( - current.on(props.service.type === "plugins" ? "plugin.updated" : "skill.updated", (event) => { + current.on(service === "plugins" ? "plugin.updated" : "skill.updated", (event) => { if (event.location?.directory !== props.directory) return void refetch() }), ), ) - return ( - + return { + get loading() { + return items.loading + }, + get ready() { + return service === "plugins" ? loaded() : data().location.skill.list({ directory: props.directory }) !== undefined + }, + get error() { + return !!items.error + }, + get count() { + return list().length + }, + get failed() { + return list().some((item) => item.status === "failed") + }, + retry: () => void refetch(), + Body: () => ( } > -

- {ctx.t(props.service.type === "plugins" ? "plugins.configured" : "skills.configured")} -

{(item) => ( -
- } + placement="top" + openDelay={0} + class="session-service-dot-tip" > - - - - +
@@ -630,6 +980,23 @@ function pluginLabel(plugin: PluginInfo) { return plugin.source.type } +function focusables(root: HTMLElement) { + return [ + ...root.querySelectorAll( + "button:not(:disabled), input:not(:disabled), a[href], [tabindex]:not([tabindex='-1'])", + ), + ] +} + +type Point = { x: number; y: number } + +function insideTriangle(point: Point, a: Point, b: Point, c: Point) { + const side = (p: Point, q: Point) => (q.x - p.x) * (point.y - p.y) - (q.y - p.y) * (point.x - p.x) + const sides = [side(a, b), side(b, c), side(c, a)] + + return sides.every((value) => value >= 0) || sides.every((value) => value <= 0) +} + // Connects, disconnects, or starts sign-in for one of the session server's MCPs, like the app's MCP toggle. function useMcpToggle(session: MountedSession, directory: () => string) { const ctx = useExtension()