mirror of
https://github.com/nexu-io/open-design.git
synced 2026-06-01 03:14:35 +07:00
* refactor(web): rename Execution mode and tighten settings dialog UI - Rename "Settings → Execution & model" to "Settings → Execution mode" across the web UI, i18n keys, docs, and e2e selectors. - Redesign SettingsDialog: kicker + title row in the modal head, a flatMap-driven agent grid that renders the inline test-result row beside the selected card, compact unavailable cards with right-aligned install/docs links, and an install guide that only shows when the user has no working agent picked. - Trim verbose subtitle / hint copy across chat model, CLI proxy, media providers, custom instructions, and memory sections. - Add an `info` Icon variant for the redesigned settings hints. - Update e2e selectors and docs that referenced the old menu label. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(web): polish Settings dialog — media providers, skills, MCP Media providers - Hide internal Stub fixture provider (settingsVisible: false) - Split provider list into Available (integrated, editable) and Coming Soon (collapsed <details> drawer with name/hint/Docs link only) - Drop right-side Integrated/Configured badges from every row; all rows in the main list are integrated by definition; inline grey "Saved" chip next to the provider name is the only status indicator now - "Saved" badge moves inline to the right of the provider name and uses a neutral grey treatment (was a standalone green pill below the name) - "Reload from daemon" button shows a 2s green "✓ Reloaded" flash on success instead of leaving a permanent paragraph under the header; errors remain sticky Skills - Replace three pill-row filter banks (Source, Type, Category) with a compact single-row toolbar: search + three inline <select> dropdowns side by side; active filter highlighted with a stronger border MCP server - Shorten section hint to one line - Move WHAT YOUR AGENT CAN DO capabilities above the client dropdown (motivate before asking to act) - Move "Build the daemon first" warning below the code block where it contextually explains why the command might fail, not as a top-level error before the user has done anything - Downgrade "Restart your client" left-border from accent orange to border-strong grey — it is a next step, not a warning External MCP - Shorten section hint to one line Misc CSS - Add .sr-only utility for accessible off-screen live regions - Add button.ghost.is-success-flash for transient success feedback - Add .library-filter-selects / .library-filter-select for dropdown filter rows - Add .media-provider-coming-soon-* for the roadmap drawer Co-authored-by: Cursor <cursoragent@cursor.com> * [codex] Add Cursor Agent auth diagnostics (#1538) * Add Cursor Agent auth diagnostics * Handle Cursor not logged in auth status * Address Cursor auth review feedback * Classify Cursor stdout auth failures * test: expand Memory and Routines coverage (#1521) * test: expand settings and packaged coverage * test: extend memory settings coverage * test: cover routine settings failure states * test: cover routine operation failures * test: fix daemon test typing on CI * test: decouple packaged smoke from orbit bug * test: avoid live memory LLM calls in route tests * test: fix daemon fetch typing in CI * fix: restore preview comment and inspect toggles * test: align manual edit flow with current inspector UX * test: align comment attachment flow with current preview comments UI * fix: probe resolved Codex launch path during detection * fix: remove duplicate board activation helper after rebase * test: update ghost cli detection mock * test: align FileViewer toolbar expectation * ci: move full app tests to extended lane * ci: run app tests by changed scope * ci: cover shared app inputs in test scopes * ci: avoid setup-node cache in windows packaged smoke * test: align extended settings and manual edit flows * refactor(web): rename Execution mode and tighten settings dialog UI - Rename "Settings → Execution & model" to "Settings → Execution mode" across the web UI, i18n keys, docs, and e2e selectors. - Redesign SettingsDialog: kicker + title row in the modal head, a flatMap-driven agent grid that renders the inline test-result row beside the selected card, compact unavailable cards with right-aligned install/docs links, and an install guide that only shows when the user has no working agent picked. - Trim verbose subtitle / hint copy across chat model, CLI proxy, media providers, custom instructions, and memory sections. - Add an `info` Icon variant for the redesigned settings hints. - Update e2e selectors and docs that referenced the old menu label. Co-authored-by: Cursor <cursoragent@cursor.com> * refactor(web): settings dialog UX polish — layout, dedup, and interactions - Remove duplicate section headers from all settings sections (Notifications, Appearance, Privacy, About, Design Systems, Skills, MCP server, Connectors, Media providers, Routines) - Restructure Notifications cards: title + toggle on same row, hint below - Restructure Skills toolbar: search + New skill button in row 1, filter dropdowns in row 2 with left-aligned labels - Restructure Pet section: tabs and Wake button on same row - MCP server: group capabilities and setup into separate cards, remove nested double border on client picker - Connectors: show connect errors as toast instead of inline card text, position toast inside panel, hide single-provider tab - Media providers: move Reload button to left-aligned small ghost button - Memory: info icon shows path on hover, Path copied badge inline; Extraction history and MEMORY.md as standalone collapsible cards; group header hidden when only one type visible - Pet grid cards: Adopt button hidden until hover, icon-only when adopted, description truncated to 2 lines, text fills full width via abs positioning - Agent cards: selected state uses accent border only, no background change - Add sun/moon icons to Appearance theme buttons (Light/Dark) - Shorten several hint strings for clarity Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): resolve i18n review comments from PR #1568 - Update settings.title and settings.envConfigure to localized "Execution mode" in all 17 non-English locale files - Add settings.memoryFlashPathCopied to all locales and use t() in MemorySection instead of hardcoded English "Path copied" - Add settings.agentModelHead to all locales and use t() in SettingsDialog for "Model for:" agent model row header Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): update tests to match settings dialog redesign - Add role prop to Toast (alert/status) so error toasts from ConnectorsBrowser are announced immediately by screen readers - Clear connectErrorToast on successful connector retry - Update SettingsDialog.execution tests: - Remove heading assertions for About and MCP server (headers were intentionally removed as duplicate nav labels) - Rewrite CLI env test to use codex-only fields (per-agent filtering means only selected agent's fields are shown) - Update Composio key hint text assertion to match shortened copy - Replace filter button click with select change for Type filter - Replace Configured/Unsupported/Integrated badge checks with updated assertions matching the new media provider UI - Replace disabled BFL row test with coming-soon section check - Update SettingsDialog.media test: remove Fal.ai input assertions (non-integrated providers no longer have editable fields) Co-authored-by: Cursor <cursoragent@cursor.com> * fix(web): unblock CI for #1568 Three small fixes to get Playwright back to green on the settings dialog redesign: 1. `en.ts`: revert `settings.envConfigure` to "Configure execution mode". This PR collapsed both `settings.title` (header gear) and `settings.envConfigure` (entry-side foot pill) to the same string "Execution mode", so `getByRole('button', { name: 'Execution mode' })` resolved to two elements and tripped Playwright strict mode in the three Composio-flow tests (entry-configuration-flows.test.ts:174, 228, 285). Restoring the distinct label also gives screen readers a clearer hint for the pill, which doubles as a status display. Non-English locales still alias the two keys; happy to follow up on those, but they don't gate the (English-only) Playwright suite. 2. entry-configuration-flows.test.ts:167 — `Connectors` heading is now rendered at `<h2>` in the modal-head (SettingsDialog.tsx:1545), with the inner `<h3>` removed by design (see comment around line 1448). Updated the assertion from `level: 3` to `level: 2`. 3. project-management-flows.test.ts:360 — same change for the `Pets` heading. Verified locally with `pnpm --filter @open-design/web typecheck` and `pnpm --filter @open-design/e2e typecheck`. The actual Playwright specs need the dev server up; I didn't rerun them here, but the locator changes are mechanical and match the new DOM. * fix(web): use exact match for Execution mode button locator Playwright's `getByRole({ name })` defaults to substring matching, so `{ name: 'Execution mode' }` still resolved to both the header gear (aria-label "Execution mode") and the entry-side foot pill (aria-label "Configure execution mode" — substring contains "Execution mode"). Strict mode tripped in the three composio-flow tests at lines 202, 257, and 319. Adding `exact: true` makes each call resolve to just the header gear, which opens the same dialog the foot pill does — the test outcomes are unchanged. --------- Co-authored-by: chaoxiaoche <chaoxiaoche@chaoxiaochedeMacBook-Pro.local> Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Caprika <56862773+alchemistklk@users.noreply.github.com> Co-authored-by: shangxinyu1 <shangxinyu@refly.ai> Co-authored-by: lefarcen <935902669@qq.com>
230 lines
6.7 KiB
TypeScript
230 lines
6.7 KiB
TypeScript
// @vitest-environment jsdom
|
|
|
|
import { cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import { within } from '@testing-library/react';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { SettingsDialog } from '../../src/components/SettingsDialog';
|
|
import { DEFAULT_CONFIG } from '../../src/state/config';
|
|
import type { AgentInfo, AppConfig } from '../../src/types';
|
|
|
|
describe('SettingsDialog media providers', () => {
|
|
afterEach(() => {
|
|
cleanup();
|
|
});
|
|
|
|
it('shows saved masked media provider keys like Composio does', () => {
|
|
renderDialog({
|
|
...DEFAULT_CONFIG,
|
|
mediaProviders: {
|
|
openai: {
|
|
apiKey: '',
|
|
apiKeyConfigured: true,
|
|
apiKeyTail: '1234',
|
|
baseUrl: '',
|
|
},
|
|
},
|
|
});
|
|
|
|
expect(screen.getByText('Saved · ••••1234')).toBeTruthy();
|
|
expect(screen.getByLabelText('OpenAI API key').getAttribute('placeholder')).toBe(
|
|
'Paste a new key to replace the saved one',
|
|
);
|
|
});
|
|
|
|
it('shows daemon fallback notice and reloads media providers from daemon', async () => {
|
|
const reloadMock = vi.fn(async () => ({
|
|
openai: {
|
|
apiKey: '',
|
|
apiKeyConfigured: true,
|
|
apiKeyTail: '9876',
|
|
baseUrl: 'https://daemon.example/v1',
|
|
},
|
|
}));
|
|
renderDialog(
|
|
{
|
|
...DEFAULT_CONFIG,
|
|
mediaProviders: {},
|
|
},
|
|
{
|
|
mediaProvidersNotice:
|
|
'Could not load media provider settings from the local daemon. Using browser-saved settings for now.',
|
|
onReloadMediaProviders: reloadMock,
|
|
},
|
|
);
|
|
|
|
expect(
|
|
screen.getByText(
|
|
'Could not load media provider settings from the local daemon. Using browser-saved settings for now.',
|
|
),
|
|
).toBeTruthy();
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Reload from daemon' }));
|
|
|
|
await waitFor(() => {
|
|
expect(reloadMock).toHaveBeenCalledTimes(1);
|
|
expect(screen.getByText('Saved · ••••9876')).toBeTruthy();
|
|
expect(screen.getByText('Reloaded media provider settings from the local daemon.')).toBeTruthy();
|
|
});
|
|
|
|
expect((screen.getByLabelText('OpenAI Base URL') as HTMLInputElement).value).toBe(
|
|
'https://daemon.example/v1',
|
|
);
|
|
});
|
|
|
|
it('preserves local-only providers when daemon reload returns a partial provider set', async () => {
|
|
const reloadMock = vi.fn(async () => ({
|
|
openai: {
|
|
apiKey: '',
|
|
apiKeyConfigured: true,
|
|
apiKeyTail: '9876',
|
|
baseUrl: 'https://daemon.example/v1',
|
|
},
|
|
}));
|
|
renderDialog(
|
|
{
|
|
...DEFAULT_CONFIG,
|
|
mediaProviders: {
|
|
openai: {
|
|
apiKey: 'sk-local-openai',
|
|
baseUrl: 'https://local-openai.example/v1',
|
|
},
|
|
fal: {
|
|
apiKey: 'sk-local-fal',
|
|
baseUrl: 'https://queue.fal.run',
|
|
model: 'fal-ai/imagen4/preview',
|
|
},
|
|
},
|
|
},
|
|
{
|
|
mediaProvidersNotice:
|
|
'Could not load media provider settings from the local daemon. Using browser-saved settings for now.',
|
|
onReloadMediaProviders: reloadMock,
|
|
},
|
|
);
|
|
|
|
fireEvent.click(screen.getByRole('button', { name: 'Reload from daemon' }));
|
|
|
|
await waitFor(() => {
|
|
expect(reloadMock).toHaveBeenCalledTimes(1);
|
|
expect(screen.getByText('Saved · ••••9876')).toBeTruthy();
|
|
expect(screen.getByText('Reloaded media provider settings from the local daemon.')).toBeTruthy();
|
|
});
|
|
|
|
expect((screen.getByLabelText('OpenAI Base URL') as HTMLInputElement).value).toBe(
|
|
'https://daemon.example/v1',
|
|
);
|
|
// Fal.ai is a non-integrated (coming-soon) provider and no longer has
|
|
// editable input fields in the UI; its config is preserved in state via
|
|
// mergeDaemonMediaProviders (covered by state/config.test.ts).
|
|
});
|
|
|
|
it('preserves saved media keys when clearing only a non-secret field', async () => {
|
|
const onPersist = vi.fn();
|
|
renderDialog(
|
|
{
|
|
...saveableConfig(),
|
|
mediaProviders: {
|
|
openai: {
|
|
apiKey: '',
|
|
apiKeyConfigured: true,
|
|
apiKeyTail: '1234',
|
|
baseUrl: 'https://custom.example/v1',
|
|
},
|
|
},
|
|
},
|
|
{ onPersist },
|
|
);
|
|
|
|
fireEvent.change(screen.getByLabelText('OpenAI Base URL'), { target: { value: '' } });
|
|
|
|
await waitFor(() => {
|
|
expect(onPersist).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
mediaProviders: {
|
|
openai: {
|
|
apiKey: '',
|
|
apiKeyConfigured: true,
|
|
apiKeyTail: '1234',
|
|
baseUrl: '',
|
|
},
|
|
},
|
|
}),
|
|
expect.objectContaining({ forceMediaProviderSync: true }),
|
|
);
|
|
});
|
|
});
|
|
|
|
it('clears saved media keys only through the explicit Clear action', async () => {
|
|
const onPersist = vi.fn();
|
|
const confirmSpy = vi.spyOn(window, 'confirm').mockReturnValue(true);
|
|
renderDialog(
|
|
{
|
|
...saveableConfig(),
|
|
mediaProviders: {
|
|
openai: {
|
|
apiKey: '',
|
|
apiKeyConfigured: true,
|
|
apiKeyTail: '1234',
|
|
baseUrl: 'https://custom.example/v1',
|
|
},
|
|
},
|
|
},
|
|
{ onPersist },
|
|
);
|
|
|
|
const openaiRow = screen.getByText('OpenAI').closest('.media-provider-row') as HTMLElement | null;
|
|
if (!openaiRow) throw new Error('Expected OpenAI media provider row');
|
|
fireEvent.click(within(openaiRow).getByRole('button', { name: 'Clear' }));
|
|
|
|
await waitFor(() => {
|
|
expect(onPersist).toHaveBeenCalledWith(
|
|
expect.objectContaining({ mediaProviders: {} }),
|
|
expect.objectContaining({ forceMediaProviderSync: true }),
|
|
);
|
|
});
|
|
|
|
expect(confirmSpy).toHaveBeenCalledTimes(1);
|
|
confirmSpy.mockRestore();
|
|
});
|
|
});
|
|
|
|
function renderDialog(
|
|
initial: AppConfig,
|
|
options?: {
|
|
mediaProvidersNotice?: string | null;
|
|
onReloadMediaProviders?: () => Promise<AppConfig['mediaProviders'] | null>;
|
|
onPersist?: (cfg: AppConfig, options?: { forceMediaProviderSync?: boolean }) => void;
|
|
},
|
|
) {
|
|
return render(
|
|
<SettingsDialog
|
|
initial={initial}
|
|
agents={SAVEABLE_AGENTS}
|
|
daemonLive
|
|
appVersionInfo={null}
|
|
initialSection="media"
|
|
onPersist={options?.onPersist ?? vi.fn()}
|
|
onPersistComposioKey={vi.fn()}
|
|
onClose={vi.fn()}
|
|
onRefreshAgents={vi.fn()}
|
|
mediaProvidersNotice={options?.mediaProvidersNotice}
|
|
onReloadMediaProviders={options?.onReloadMediaProviders}
|
|
/>,
|
|
);
|
|
}
|
|
|
|
const SAVEABLE_AGENTS: AgentInfo[] = [
|
|
{
|
|
id: 'codex',
|
|
name: 'Codex',
|
|
bin: 'codex',
|
|
available: true,
|
|
},
|
|
];
|
|
|
|
function saveableConfig(): AppConfig {
|
|
return {
|
|
...DEFAULT_CONFIG,
|
|
agentId: 'codex',
|
|
};
|
|
}
|