You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by zh...@apache.org on 2026/09/18 10:19:28 UTC

(maka) branch main updated: fix(desktop): avoid eager model-row tooltip mounting (#5467)

This is an automated email from the ASF dual-hosted git repository.

me2seeks pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/maka.git


The following commit(s) were added to refs/heads/main by this push:
     new 3abdf3fd74 fix(desktop): avoid eager model-row tooltip mounting (#5467)
3abdf3fd74 is described below

commit 3abdf3fd74c72d3b4bf82957431dcdbd8d8de756
Author: testikun <bu...@gmail.com>
AuthorDate: Fri Sep 18 19:19:23 2026 +0900

    fix(desktop): avoid eager model-row tooltip mounting (#5467)
    
    Use a native hover hint for model parameter actions, retaining the accessible label without creating a portal for every catalog row. Add credential-free large-inventory coverage and a repeatable browser performance probe.
    
    Generated-by: Codex
    
    Co-authored-by: testikun <32...@users.noreply.github.com>
---
 .../connection-settings-locale-render.test.ts      |  37 ++++++
 .../settings/provider-connection-detail.tsx        |   9 +-
 .../stories/settings/provider-settings.stories.tsx |  32 ++++++
 scripts/perf/model-connection-open.mjs             | 127 +++++++++++++++++++++
 4 files changed, 202 insertions(+), 3 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/connection-settings-locale-render.test.ts b/apps/desktop/src/main/__tests__/connection-settings-locale-render.test.ts
index 0fd1c6a4bd..fca1aff52f 100644
--- a/apps/desktop/src/main/__tests__/connection-settings-locale-render.test.ts
+++ b/apps/desktop/src/main/__tests__/connection-settings-locale-render.test.ts
@@ -384,6 +384,43 @@ test('credential probing does not flash a page-level loading warning', async ()
   });
 });
 
+test('model rows retain named parameter actions without mounting a tooltip layer per row', async () => {
+  const harness = installRenderer();
+  const base = relayConnection();
+  const models = Array.from({ length: 32 }, (_, index) => ({
+    id: `fixture/model-${index + 1}`,
+    displayName: `Fixture model ${index + 1}`,
+  }));
+  const connection: ProjectedLlmConnection = {
+    ...base,
+    defaultModel: models[0]!.id,
+    enabledModelIds: [models[0]!.id],
+    models,
+    catalogEntries: models.map((model, index) => ({
+      ...base.catalogEntries[0]!,
+      ...model,
+      isDefault: index === 0,
+    })),
+  };
+  await harness.render('zh-CN', createElement(components.RuntimeHostSettingsTarget, {
+    host: { profileId: 'local', hostId: 'host-local' },
+    children: createElement(components.ConnectionDetail, {
+      bridge: connectionDetailBridge({ hasSecret: async () => false }),
+      connection,
+      isDefault: true,
+      onChanged: async () => {},
+      onDeleted: async () => {},
+    }),
+  }));
+
+  const actions = [...harness.document.querySelectorAll<HTMLButtonElement>('span[title="配置参数"] > button')];
+  assert.equal(actions.length, models.length);
+  for (const [index, action] of actions.entries()) {
+    assert.equal(action.getAttribute('aria-label'), `配置模型参数:${models[index]!.displayName}`);
+    assert.equal(action.hasAttribute('aria-describedby'), false);
+  }
+});
+
 test('credential read failures still render the persistent warning', async () => {
   const harness = installRenderer();
   const credential = deferred<boolean>();
diff --git a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
index 98010a1fee..b690092c8f 100644
--- a/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
+++ b/apps/desktop/src/renderer/settings/provider-connection-detail.tsx
@@ -673,9 +673,12 @@ function ConnectionDetailInner(props: ConnectionDetailProps) {
             ) : label;
             return (
                 <SettingsRow key={id} label={rowLabel} end={<>
-                  <IconButton variant="ghost" size="sm" icon={<Icon icon="wrench" size="sm" />}
-                    label={copy.declareCapabilitiesAria(label)} tooltip={copy.declareCapabilities}
-                    isDisabled={allActionsBusy} onClick={() => openRow({ model: id })} />
+                  {/* Astryx tooltips resolve a portal on mount, causing a style recalculation per model row. */}
+                  <span title={copy.declareCapabilities}>
+                    <IconButton variant="ghost" size="sm" icon={<Icon icon="wrench" size="sm" />}
+                      label={copy.declareCapabilitiesAria(label)}
+                      isDisabled={allActionsBusy} onClick={() => openRow({ model: id })} />
+                  </span>
                   {modelEnableSwitch(id, label)}
                 </>} />
 
diff --git a/apps/desktop/stories/settings/provider-settings.stories.tsx b/apps/desktop/stories/settings/provider-settings.stories.tsx
index 8586079cc6..0819df57cf 100644
--- a/apps/desktop/stories/settings/provider-settings.stories.tsx
+++ b/apps/desktop/stories/settings/provider-settings.stories.tsx
@@ -61,6 +61,7 @@ type AutoOpenTarget =
   | 'detail-alibaba'
   | 'detail-static'
   | 'detail-relay'
+  | 'detail-large'
   | 'add'
   | 'catalog'
   | 'oauth'
@@ -137,6 +138,18 @@ const configuredConnections = [
   }),
 ];
 
+const largeConnection = makeConnection({
+  slug: 'openrouter-large',
+  name: 'OpenRouter',
+  providerType: 'openrouter',
+  defaultModel: 'fixture/model-001',
+  models: Array.from({ length: 444 }, (_, index) => ({
+    id: `fixture/model-${String(index + 1).padStart(3, '0')}`,
+    displayName: `Fixture model ${String(index + 1).padStart(3, '0')}`,
+  })),
+  modelSource: 'fetched',
+});
+
 const alibabaTokenPlanConnections = [
   makeConnection({
     slug: 'alibaba-token-plan-cn',
@@ -686,6 +699,7 @@ function clickAutoOpenTarget(root: HTMLElement, target: AutoOpenTarget): boolean
     || target === 'detail-alibaba'
     || target === 'detail-static'
     || target === 'detail-relay'
+    || target === 'detail-large'
   ) {
     // ListItem's clickable surface is an invisible button inside the row, so
     // the row is located by its slug hook and the button taken from within it.
@@ -696,6 +710,8 @@ function clickAutoOpenTarget(root: HTMLElement, target: AutoOpenTarget): boolean
           ? 'alibaba-token-plan-cn'
           : target === 'detail-static'
             ? 'ark-plan'
+            : target === 'detail-large'
+              ? 'openrouter-large'
             : 'relay-house';
     const row = root.querySelector<HTMLElement>(`[data-connection-slug="${slug}"]`);
     const detailButton = row?.querySelector('button') ?? null;
@@ -790,6 +806,22 @@ export const ConnectionDetailPage: Story = {
   ),
 };
 
+// Real path: 设置 → 模型 → an OpenRouter connection after fetching hundreds of models.
+// The fetched snapshot is local; browsing it never needs an API key.
+export const LargeConnectionDetail: Story = {
+  render: () => (
+    <ProviderStory
+      bridge={createBridge({ connections: [largeConnection], defaultSlug: 'openrouter-large' })}
+      autoOpen="detail-large"
+    />
+  ),
+  play: async ({ canvasElement }) => {
+    await waitFor(() => {
+      expect(canvasElement.querySelectorAll('button[aria-label*="Fixture model"]').length).toBe(444);
+    }, { timeout: 20_000 });
+  },
+};
+
 // Fixed endpoints are inspectable but not editable. Alibaba is the high-signal
 // case because several catalog entries share one brand while routing to
 // different products and regions (#3636).
diff --git a/scripts/perf/model-connection-open.mjs b/scripts/perf/model-connection-open.mjs
new file mode 100644
index 0000000000..ae6d05b70f
--- /dev/null
+++ b/scripts/perf/model-connection-open.mjs
@@ -0,0 +1,127 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+// Build Storybook first, then run from the repository root. No provider key or Host is used.
+import assert from 'node:assert/strict';
+import { chromium, expect } from '@playwright/test';
+import { startStaticServer } from '../storybook-visual-smoke.mjs';
+
+const server = await startStaticServer('apps/desktop/storybook-static');
+const browser = await chromium.launch(
+  process.env.MAKA_PERF_CHROME_CHANNEL ? { channel: process.env.MAKA_PERF_CHROME_CHANNEL } : {},
+);
+try {
+  const page = await browser.newPage({ viewport: { width: 1200, height: 900 } });
+  await page.addInitScript(() => {
+    const probe = (window.__modelOpenProbe = { started: 0, completed: 0, longTasks: [] });
+    new PerformanceObserver((list) => {
+      probe.longTasks.push(
+        ...list.getEntries().map((entry) => ({
+          start: entry.startTime,
+          duration: entry.duration,
+        })),
+      );
+    }).observe({ type: 'longtask', buffered: true });
+    document.addEventListener(
+      'click',
+      (event) => {
+        if (!event.target.closest('[data-connection-slug="openrouter-large"]')) return;
+        probe.started = performance.now();
+        const afterPaint = () =>
+          requestAnimationFrame(() =>
+            requestAnimationFrame(() => {
+              probe.completed = performance.now();
+              probe.rows = document.querySelectorAll('button[aria-label*="Fixture model"]').length;
+              probe.nodes = document.querySelectorAll('*').length;
+            }),
+          );
+        const waitForRows = () => {
+          if (document.querySelectorAll('button[aria-label*="Fixture model"]').length === 444) {
+            afterPaint();
+          } else {
+            requestAnimationFrame(waitForRows);
+          }
+        };
+        requestAnimationFrame(waitForRows);
+      },
+      { capture: true },
+    );
+  });
+
+  const samples = [];
+  for (let i = 0; i < 5; i++) {
+    await page.goto(
+      `${server.baseUrl}/iframe.html?id=product-settings-providers--large-connection-detail&viewMode=story`,
+    );
+    await page.waitForFunction(() => window.__modelOpenProbe?.completed > 0, null, {
+      timeout: 30_000,
+    });
+    const sample = await page.evaluate(() => {
+      const { started, completed, longTasks, rows, nodes } = window.__modelOpenProbe;
+      const duringOpen = longTasks.filter(
+        (task) => task.start + task.duration >= started && task.start <= completed,
+      );
+      return {
+        openMs: Math.round(completed - started),
+        longestTaskMs: Math.round(Math.max(0, ...duringOpen.map((task) => task.duration))),
+        rows,
+        nodes,
+      };
+    });
+    assert.equal(sample.rows, 444);
+    samples.push(sample);
+  }
+  const filter = page.getByRole('textbox', { name: /搜索模型|Search models/ });
+  await filter.fill('fixture/model-444');
+  const action = page.getByRole('button', { name: /Fixture model 444/ });
+  const enabled = page.getByRole('switch', { name: /Fixture model 444/ });
+  await expect(action).toHaveCount(1);
+  await expect(enabled).not.toBeChecked();
+  await enabled.click();
+  await expect(enabled).toBeChecked();
+  await action.click();
+  await expect(page.getByRole('dialog')).toBeVisible();
+  await page.keyboard.press('Escape');
+  await expect(page.getByRole('dialog')).toHaveCount(0);
+  await filter.fill('Fixture model 001');
+  await expect(page.getByRole('button', { name: /Fixture model 001/ })).toHaveCount(1);
+  await filter.fill('');
+  await expect(page.locator('button[aria-label*="Fixture model"]')).toHaveCount(444);
+  if (process.env.MAKA_PERF_SCREENSHOT) {
+    await page.screenshot({ path: process.env.MAKA_PERF_SCREENSHOT });
+  }
+  await page.setViewportSize({ width: 480, height: 800 });
+  await filter.fill('fixture/model-444');
+  await expect(action).toBeVisible();
+  const actionBounds = await action.boundingBox();
+  const switchBounds = await enabled.boundingBox();
+  assert.ok(actionBounds && switchBounds);
+  assert.ok(
+    actionBounds.x + actionBounds.width <= switchBounds.x ||
+      actionBounds.y + actionBounds.height <= switchBounds.y,
+    'the parameter action must not overlap its enable switch',
+  );
+  if (process.env.MAKA_PERF_MOBILE_SCREENSHOT) {
+    await page.screenshot({ path: process.env.MAKA_PERF_MOBILE_SCREENSHOT });
+  }
+  console.log(JSON.stringify(samples, null, 2));
+} finally {
+  await browser.close();
+  await server.close();
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org