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>
199 lines
6.3 KiB
TypeScript
199 lines
6.3 KiB
TypeScript
import type { Dispatch, SetStateAction } from 'react';
|
|
import { useT } from '../i18n';
|
|
import { Icon } from './Icon';
|
|
import type { AppConfig, TelemetryConfig } from '../types';
|
|
|
|
interface Props {
|
|
cfg: AppConfig;
|
|
setCfg: Dispatch<SetStateAction<AppConfig>>;
|
|
}
|
|
|
|
function generateInstallationId(): string {
|
|
if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
|
|
return crypto.randomUUID();
|
|
}
|
|
// Older webviews / test runners that lack crypto.randomUUID. The output
|
|
// is opaque and non-PII; we only need uniqueness across installs.
|
|
return `inst-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
|
|
}
|
|
|
|
export function PrivacySection({ cfg, setCfg }: Props): JSX.Element {
|
|
const t = useT();
|
|
const telemetry: TelemetryConfig = cfg.telemetry ?? {};
|
|
// `privacyDecisionAt` gates the consent surface. installationId is only
|
|
// the anonymous reporting id and can be rotated by Delete my data without
|
|
// making the first-run banner appear again.
|
|
const hasMadeConsentDecision = cfg.privacyDecisionAt != null;
|
|
|
|
function patchTelemetry(patch: Partial<TelemetryConfig>): void {
|
|
setCfg((c) => {
|
|
const nextTelemetry = { ...(c.telemetry ?? {}), ...patch };
|
|
const shouldHaveId = Object.values(nextTelemetry).some((v) => v === true);
|
|
return {
|
|
...c,
|
|
installationId:
|
|
shouldHaveId && !c.installationId
|
|
? generateInstallationId()
|
|
: c.installationId,
|
|
privacyDecisionAt: Date.now(),
|
|
telemetry: nextTelemetry,
|
|
};
|
|
});
|
|
}
|
|
|
|
function shareUsage(): void {
|
|
setCfg((c) => ({
|
|
...c,
|
|
installationId: generateInstallationId(),
|
|
privacyDecisionAt: Date.now(),
|
|
telemetry: { metrics: true, content: true, artifactManifest: false },
|
|
}));
|
|
}
|
|
|
|
function declineUsage(): void {
|
|
setCfg((c) => ({
|
|
...c,
|
|
installationId: null,
|
|
privacyDecisionAt: Date.now(),
|
|
telemetry: { metrics: false, content: false, artifactManifest: false },
|
|
}));
|
|
}
|
|
|
|
function deleteMyData(): void {
|
|
setCfg((c) => ({
|
|
...c,
|
|
installationId: generateInstallationId(),
|
|
privacyDecisionAt: c.privacyDecisionAt ?? Date.now(),
|
|
telemetry: { metrics: false, content: false, artifactManifest: false },
|
|
}));
|
|
}
|
|
|
|
return (
|
|
<section className="settings-section">
|
|
{!hasMadeConsentDecision ? (
|
|
<ConsentCard onShare={shareUsage} onDecline={declineUsage} />
|
|
) : (
|
|
<>
|
|
<div className="settings-privacy-toggles">
|
|
<ToggleRow
|
|
label={t('settings.privacyMetrics')}
|
|
hint={t('settings.privacyMetricsHint')}
|
|
checked={telemetry.metrics === true}
|
|
onChange={(v) => patchTelemetry({ metrics: v })}
|
|
/>
|
|
<ToggleRow
|
|
label={t('settings.privacyContent')}
|
|
hint={t('settings.privacyContentHint')}
|
|
checked={telemetry.content === true}
|
|
onChange={(v) => patchTelemetry({ content: v })}
|
|
/>
|
|
<ToggleRow
|
|
label={t('settings.privacyArtifacts')}
|
|
hint={t('settings.privacyArtifactsHint')}
|
|
checked={telemetry.artifactManifest === true}
|
|
onChange={(v) => patchTelemetry({ artifactManifest: v })}
|
|
/>
|
|
</div>
|
|
|
|
<div className="settings-subsection">
|
|
<div className="section-head">
|
|
<div>
|
|
<h4>{t('settings.privacyInstallationId')}</h4>
|
|
<p className="hint">{t('settings.privacyDataDeletionHint')}</p>
|
|
</div>
|
|
</div>
|
|
<div className="settings-field">
|
|
<input
|
|
type="text"
|
|
readOnly
|
|
value={cfg.installationId ?? t('settings.privacyOptedOut')}
|
|
aria-label={t('settings.privacyInstallationId')}
|
|
/>
|
|
</div>
|
|
<button
|
|
type="button"
|
|
className="ghost"
|
|
onClick={deleteMyData}
|
|
style={{ alignSelf: 'flex-start', marginTop: 12 }}
|
|
>
|
|
<Icon name="trash" size={13} />
|
|
<span style={{ marginLeft: 6 }}>{t('settings.privacyDataDeletion')}</span>
|
|
</button>
|
|
</div>
|
|
</>
|
|
)}
|
|
</section>
|
|
);
|
|
}
|
|
|
|
interface ToggleRowProps {
|
|
label: string;
|
|
hint: string;
|
|
checked: boolean;
|
|
onChange: (next: boolean) => void;
|
|
}
|
|
|
|
// Reuses .toggle-row (label + hint + iOS-style switch) — same control
|
|
// NewProjectPanel uses for "speaker notes" / "animations" toggles, so the
|
|
// Privacy panel reads as native to the rest of the app.
|
|
function ToggleRow({ label, hint, checked, onChange }: ToggleRowProps): JSX.Element {
|
|
return (
|
|
<button
|
|
type="button"
|
|
className={`toggle-row${checked ? ' on' : ''}`}
|
|
onClick={() => onChange(!checked)}
|
|
aria-pressed={checked}
|
|
>
|
|
<div className="toggle-row-text">
|
|
<span className="toggle-row-label">{label}</span>
|
|
<span className="toggle-row-hint">{hint}</span>
|
|
</div>
|
|
<span className="toggle-row-switch" aria-hidden />
|
|
</button>
|
|
);
|
|
}
|
|
|
|
interface ConsentProps {
|
|
onShare: () => void;
|
|
onDecline: () => void;
|
|
}
|
|
|
|
function ConsentCard({ onShare, onDecline }: ConsentProps): JSX.Element {
|
|
const t = useT();
|
|
return (
|
|
<div className="settings-subsection">
|
|
<div className="section-head">
|
|
<div>
|
|
<h4>{t('settings.privacyConsentKicker')}</h4>
|
|
<p className="hint">{t('settings.privacyConsentLead')}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<dl className="settings-privacy-disclosure">
|
|
<div>
|
|
<dt>{t('settings.privacyMetrics')}</dt>
|
|
<dd>{t('settings.privacyMetricsHint')}</dd>
|
|
</div>
|
|
<div>
|
|
<dt>{t('settings.privacyContent')}</dt>
|
|
<dd>{t('settings.privacyContentHint')}</dd>
|
|
</div>
|
|
</dl>
|
|
|
|
<p className="hint">{t('settings.privacyConsentFooter')}</p>
|
|
|
|
<div
|
|
className="privacy-consent-actions"
|
|
role="group"
|
|
aria-label={t('settings.privacyConsentKicker')}
|
|
>
|
|
<button type="button" className="privacy-consent-action" onClick={onDecline}>
|
|
{t('settings.privacyConsentDecline')}
|
|
</button>
|
|
<button type="button" className="privacy-consent-action" onClick={onShare}>
|
|
{t('settings.privacyConsentShare')}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|