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()