@nextclaw/ui 0.15.7 → 0.15.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/dist/assets/{api-V8m62-13.js → api-kGIZo6kv.js} +1 -1
  3. package/dist/assets/{app-presenter-provider-Bzj9h_ZS.js → app-presenter-provider-Ct0DePOC.js} +48 -48
  4. package/dist/assets/{appearance-settings-page-DwBwu1AU.js → appearance-settings-page-CcA8Kewq.js} +1 -1
  5. package/dist/assets/{book-open-DtVSr5O9.js → book-open-D4LvR1N9.js} +1 -1
  6. package/dist/assets/{channels-list-page-CYIqMliJ.js → channels-list-page-CybW_0nu.js} +1 -1
  7. package/dist/assets/{chat-page-BC0icTtI.js → chat-page-Bs8c1adx.js} +1 -1
  8. package/dist/assets/{config-split-page-B88prd8s.js → config-split-page-BvhZDJGS.js} +1 -1
  9. package/dist/assets/{confirm-dialog-BB09IC2u.js → confirm-dialog-Ucx_SQm4.js} +1 -1
  10. package/dist/assets/desktop-update-config-Ch9m1GRB.js +1 -0
  11. package/dist/assets/{dist-Bytser-a.js → dist-CCwmPmQ3.js} +1 -1
  12. package/dist/assets/doc-browser-Cr7330LU2.js +1 -0
  13. package/dist/assets/{doc-browser-B1FXvZm6.js → doc-browser-N9UwkWvd.js} +1 -1
  14. package/dist/assets/{doc-browser-context-D5VLNoj3.js → doc-browser-context-CIIKT2sE.js} +1 -1
  15. package/dist/assets/{download-CdO6NoHj.js → download-CSAHAjF9.js} +1 -1
  16. package/dist/assets/{external-link-C5NFoh5I.js → external-link-Dno-cJJr.js} +1 -1
  17. package/dist/assets/{eye-B51oNRRc.js → eye-BZkraQUn.js} +1 -1
  18. package/dist/assets/{folder-DxH7Nef-.js → folder-Bro2sQD3.js} +1 -1
  19. package/dist/assets/{form-actions-C9pUwtYV.js → form-actions-CC31CR28.js} +1 -1
  20. package/dist/assets/index-BlKbckX4.css +1 -0
  21. package/dist/assets/{index-UnY4HtVa.js → index-EO5Gsaff.js} +2 -2
  22. package/dist/assets/loader-circle-DyToFBCM.js +1 -0
  23. package/dist/assets/{logo-badge-D2vORX3a.js → logo-badge-B___Witw.js} +1 -1
  24. package/dist/assets/mcp-marketplace-page-3Q-VOIJ3.js +1 -0
  25. package/dist/assets/{mcp-marketplace-page-CGVu7mfL.js → mcp-marketplace-page-Ccy-UG8R.js} +1 -1
  26. package/dist/assets/{model-config-page-DYpchGVE.js → model-config-page-BsMxhVaO.js} +1 -1
  27. package/dist/assets/plus-wynE6QCr.js +1 -0
  28. package/dist/assets/{popover-CONujDHf.js → popover-CAy9XSdY.js} +1 -1
  29. package/dist/assets/{provider-scoped-model-input-DA3Ua9v8.js → provider-scoped-model-input-Cqo78Dx_.js} +1 -1
  30. package/dist/assets/{providers-config-page-DXsoyMrh.js → providers-config-page-CZSc-ruy.js} +1 -1
  31. package/dist/assets/react-UuyIZ_Oi.js +8 -0
  32. package/dist/assets/{refresh-cw-C6j2tjul.js → refresh-cw-w9L_vlM-.js} +1 -1
  33. package/dist/assets/remote-D4rv7kb-.js +1 -0
  34. package/dist/assets/rotate-cw-C0VFtLju.js +1 -0
  35. package/dist/assets/{runtime-config-page-eYrrKBrA.js → runtime-config-page-DM63Nkxm.js} +1 -1
  36. package/dist/assets/{save-CBBZVvZ-.js → save-DdL5VkJS.js} +1 -1
  37. package/dist/assets/{search-DFF842z2.js → search-DydusQAS.js} +1 -1
  38. package/dist/assets/{search-config-page-DUyuwD7d.js → search-config-page-CfwWop50.js} +1 -1
  39. package/dist/assets/{secrets-config-page-DwzKQJ76.js → secrets-config-page-BPIZUwLW.js} +1 -1
  40. package/dist/assets/security-config-CeBjHoFL.js +1 -0
  41. package/dist/assets/{select-BiOpo2kV.js → select-BEtjsC3L.js} +1 -1
  42. package/dist/assets/{setting-row-C3WQXLtS.js → setting-row-CTA16hhx.js} +1 -1
  43. package/dist/assets/{settings-2-DyOvA_6K.js → settings-2-D6BrZlD5.js} +1 -1
  44. package/dist/assets/{skeleton-CQIAEbA4.js → skeleton-DcJKUZCH.js} +1 -1
  45. package/dist/assets/{tabs-BTSENlI2.js → tabs-CrPVCYHg.js} +1 -1
  46. package/dist/assets/{tag-chip-BG5JIGwY.js → tag-chip-Bvm-V7SR.js} +1 -1
  47. package/dist/assets/{trash-2-CTIfwpt1.js → trash-2-5ABWNTB-.js} +1 -1
  48. package/dist/assets/{use-config-CY4uF2Ou.js → use-config-DJaZYyZC.js} +1 -1
  49. package/dist/assets/{use-confirm-dialog-Bj4u78F9.js → use-confirm-dialog-QUWL1EuB.js} +1 -1
  50. package/dist/assets/{use-viewport-layout-ChJUcSQ6.js → use-viewport-layout-BWYz8zwd.js} +1 -1
  51. package/dist/assets/x-gM1Q7cRX.js +1 -0
  52. package/dist/index.html +29 -29
  53. package/package.json +7 -7
  54. package/src/features/chat/features/input/input-surface-plugins/__tests__/context-reference-plugin.test.ts +3 -3
  55. package/src/features/chat/features/input/input-surface-plugins/context-reference-plugin.utils.ts +2 -2
  56. package/src/features/system-status/components/__tests__/desktop-update-config.test.tsx +21 -0
  57. package/src/features/system-status/components/desktop-update-config.tsx +8 -5
  58. package/src/shared/components/common/__tests__/brand-header.test.tsx +39 -17
  59. package/src/shared/components/common/brand-header.tsx +55 -51
  60. package/src/shared/lib/i18n/locales/en-US/chat.json +1 -1
  61. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +4 -0
  62. package/src/shared/lib/i18n/locales/zh-CN/chat.json +1 -1
  63. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +4 -0
  64. package/dist/assets/desktop-update-config-B-q0OhxO.js +0 -1
  65. package/dist/assets/doc-browser-Bodhf0fj2.js +0 -1
  66. package/dist/assets/index-DtWf7kk9.css +0 -1
  67. package/dist/assets/loader-circle-BRSfaztA.js +0 -1
  68. package/dist/assets/mcp-marketplace-page-DnV0e1JS.js +0 -1
  69. package/dist/assets/plus-BgMBV9HJ.js +0 -1
  70. package/dist/assets/react-Io6Pfq8D.js +0 -8
  71. package/dist/assets/remote-C1y3ARHu.js +0 -1
  72. package/dist/assets/rotate-cw-Bv2B6fAV.js +0 -1
  73. package/dist/assets/security-config-D8me3pXl.js +0 -1
  74. package/dist/assets/x-C6cM8wkT.js +0 -1
@@ -145,6 +145,27 @@ describe('DesktopUpdateConfig', () => {
145
145
  expect(screen.getByText('50 B / 100 B')).toBeTruthy();
146
146
  });
147
147
 
148
+ it('labels a failed check separately from an update failure', () => {
149
+ useRuntimeUpdateStore.setState((state) => ({
150
+ ...state,
151
+ snapshot: state.snapshot
152
+ ? {
153
+ ...state.snapshot,
154
+ status: 'failed',
155
+ currentVersion: '0.24.0',
156
+ failureStage: 'check',
157
+ errorMessage: 'fetch failed: getaddrinfo ENOTFOUND updates.nextclaw.io'
158
+ }
159
+ : null
160
+ }));
161
+
162
+ renderDesktopUpdateConfig();
163
+
164
+ expect(screen.getByText('检查更新失败')).toBeTruthy();
165
+ expect(screen.getByText('0.24.0')).toBeTruthy();
166
+ expect(screen.getByText('fetch failed: getaddrinfo ENOTFOUND updates.nextclaw.io')).toBeTruthy();
167
+ });
168
+
148
169
  it('loads structured release notes from the docs JSON endpoint', async () => {
149
170
  const fetchMock = vi.fn(async () => new Response(JSON.stringify({
150
171
  schemaVersion: 1,
@@ -36,8 +36,8 @@ const RELEASE_NOTES_KIND_LABEL_KEYS: Record<string, string> = {
36
36
  compatibility: 'desktopUpdatesReleaseNotesCompatibility'
37
37
  };
38
38
 
39
- function StatusBadge({ status }: { status: string }) {
40
- return <span className={cn('inline-flex rounded-full px-3 py-1 text-xs font-medium ring-1', getStatusTone(status))}>{getStatusLabel(status)}</span>;
39
+ function StatusBadge({ snapshot }: { snapshot: UpdateSnapshot }) {
40
+ return <span className={cn('inline-flex rounded-full px-3 py-1 text-xs font-medium ring-1', getStatusTone(snapshot.status))}>{getStatusLabel(snapshot)}</span>;
41
41
  }
42
42
 
43
43
  function OverviewStat({ label, value }: { label: string; value: string }) {
@@ -116,8 +116,11 @@ function formatBytes(value: number): string {
116
116
  function getChannelLabel(channel: UpdateSnapshot['channel']): string {
117
117
  return channel === 'beta' ? t('desktopUpdatesChannelBeta') : t('desktopUpdatesChannelStable');
118
118
  }
119
- function getStatusLabel(status: string): string {
120
- return t(STATUS_LABEL_KEYS[status] ?? 'desktopUpdatesStatusIdle');
119
+ function getStatusLabel(snapshot: UpdateSnapshot): string {
120
+ if (snapshot.status === 'failed' && snapshot.failureStage) {
121
+ return t(`desktopUpdatesFailureStage.${snapshot.failureStage}`);
122
+ }
123
+ return t(STATUS_LABEL_KEYS[snapshot.status] ?? 'desktopUpdatesStatusIdle');
121
124
  }
122
125
  function getStatusTone(status: string): string {
123
126
  if (status === 'downloaded') {
@@ -256,7 +259,7 @@ export function DesktopUpdateConfig() {
256
259
  <CardDescription>{t('desktopUpdatesOverviewDescription')}</CardDescription>
257
260
  </CardHeader>
258
261
  <CardContent className="space-y-5">
259
- <div className="flex flex-wrap items-center gap-3"><span className="text-sm font-medium text-gray-700">{t('desktopUpdatesStatusLabel')}</span><StatusBadge status={snapshot.status} /></div>
262
+ <div className="flex flex-wrap items-center gap-3"><span className="text-sm font-medium text-gray-700">{t('desktopUpdatesStatusLabel')}</span><StatusBadge snapshot={snapshot} /></div>
260
263
  <div className="grid gap-4 md:grid-cols-2 xl:grid-cols-5">{overviewStats.map(([label, value]) => <OverviewStat key={label} label={label} value={value} />)}</div>
261
264
  {snapshot.channel === 'beta' ? (
262
265
  <div className="rounded-2xl border border-amber-200 bg-amber-50/70 p-4">
@@ -1,4 +1,4 @@
1
- import { render, screen } from '@testing-library/react';
1
+ import { fireEvent, render, screen } from '@testing-library/react';
2
2
  import userEvent from '@testing-library/user-event';
3
3
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4
4
  import { MemoryRouter } from 'react-router-dom';
@@ -103,7 +103,7 @@ describe('BrandHeader', () => {
103
103
  availableVersion: '0.18.12',
104
104
  downloadedVersion: null,
105
105
  minimumHostVersion: null,
106
- releaseNotesUrl: null,
106
+ releaseNotesUrl: 'https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12',
107
107
  lastCheckedAt: null,
108
108
  progress: {
109
109
  downloadedBytes: 50,
@@ -130,8 +130,17 @@ describe('BrandHeader', () => {
130
130
  expect(screen.getAllByText('v0.18.11')).toHaveLength(1);
131
131
  await user.hover(version);
132
132
  expect((await screen.findByRole('tooltip')).textContent).toBe('v0.18.11');
133
- expect(screen.getByText('下载 50%')).toBeTruthy();
133
+ const downloadProgress = screen.getByText('下载 50%');
134
+ expect(downloadProgress).toBeTruthy();
134
135
  expect(screen.queryByRole('button', { name: '更新' })).toBeNull();
136
+ const progressReleaseNotes = screen.getByRole('button', { name: '查看 v0.18.12 更新说明' });
137
+ expect(progressReleaseNotes.parentElement?.className).toContain('invisible');
138
+ expect(progressReleaseNotes.parentElement?.className).toContain('group-hover/update-release-notes:visible');
139
+
140
+ await user.hover(downloadProgress);
141
+ await user.click(progressReleaseNotes);
142
+
143
+ expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12');
135
144
  });
136
145
 
137
146
  it('opens release notes from the current version tooltip when that version has docs notes', async () => {
@@ -201,15 +210,16 @@ describe('BrandHeader', () => {
201
210
  await user.click(currentVersion);
202
211
  expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-14-nextclaw-v0-18-11');
203
212
 
204
- await user.click(screen.getByRole('button', { name: '下载' }));
205
- expect(mocks.downloadUpdate).toHaveBeenCalledTimes(1);
206
-
213
+ const downloadUpdate = screen.getByRole('button', { name: '下载' });
207
214
  const updateReleaseNotes = screen.getByRole('button', { name: '查看 v0.18.12 更新说明' });
208
- await user.hover(updateReleaseNotes);
209
- expect((await screen.findByRole('tooltip')).textContent).toBe('查看 v0.18.12 更新说明');
215
+ expect(updateReleaseNotes.parentElement?.className).toContain('group-hover/update-release-notes:visible');
210
216
 
217
+ await user.hover(downloadUpdate);
211
218
  await user.click(updateReleaseNotes);
212
- expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12');
219
+ expect(mocks.openExternalUrl).toHaveBeenLastCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12');
220
+
221
+ await user.click(downloadUpdate);
222
+ expect(mocks.downloadUpdate).toHaveBeenCalledTimes(1);
213
223
  });
214
224
 
215
225
  it('applies the downloaded update from the version-adjacent update button', async () => {
@@ -227,7 +237,7 @@ describe('BrandHeader', () => {
227
237
  availableVersion: null,
228
238
  downloadedVersion: '0.18.12',
229
239
  minimumHostVersion: null,
230
- releaseNotesUrl: null,
240
+ releaseNotesUrl: 'https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12',
231
241
  lastCheckedAt: null,
232
242
  progress: null,
233
243
  canAutoDownload: true,
@@ -245,7 +255,15 @@ describe('BrandHeader', () => {
245
255
 
246
256
  renderBrandHeader();
247
257
 
248
- await user.click(screen.getByRole('button', { name: '更新' }));
258
+ const applyUpdate = screen.getByRole('button', { name: '更新' });
259
+ const updateReleaseNotes = screen.getByRole('button', { name: '查看 v0.18.12 更新说明' });
260
+ expect(updateReleaseNotes.parentElement?.className).toContain('group-focus-within/update-release-notes:visible');
261
+
262
+ fireEvent.focus(applyUpdate);
263
+ await user.click(updateReleaseNotes);
264
+ expect(mocks.openExternalUrl).toHaveBeenCalledWith('https://docs.nextclaw.io/zh/notes/2026-07-15-nextclaw-v0-18-12');
265
+
266
+ await user.click(applyUpdate);
249
267
 
250
268
  expect(mocks.applyDownloadedUpdate).toHaveBeenCalledTimes(1);
251
269
  expect(mocks.downloadUpdate).not.toHaveBeenCalled();
@@ -299,7 +317,7 @@ describe('BrandHeader', () => {
299
317
  expect(tooltip.textContent).toContain('Set NEXTCLAW_UPDATE_BUNDLE_PUBLIC_KEY');
300
318
  });
301
319
 
302
- it('uses the failed update wording only for failed snapshots', async () => {
320
+ it('distinguishes a failed post-restart check from a failed update', async () => {
303
321
  const user = userEvent.setup();
304
322
  useRuntimeUpdateStore.setState({
305
323
  supported: true,
@@ -322,7 +340,9 @@ describe('BrandHeader', () => {
322
340
  requiresRestart: false,
323
341
  blockReason: null,
324
342
  recoveryCommand: null,
325
- errorMessage: 'runtime bundle sha256 mismatch',
343
+ errorMessage: 'fetch failed: getaddrinfo ENOTFOUND updates.nextclaw.io',
344
+ failureStage: 'check',
345
+ diagnosticCommand: 'nextclaw logs path',
326
346
  preferences: {
327
347
  automaticChecks: true,
328
348
  autoDownload: true
@@ -330,16 +350,18 @@ describe('BrandHeader', () => {
330
350
  }
331
351
  });
332
352
 
333
- renderBrandHeader();
353
+ renderBrandHeader({ productVersion: '0.24.0' });
334
354
 
335
- const issueIcon = screen.getByLabelText('更新失败');
355
+ expect(screen.getByText('v0.24.0')).not.toBeNull();
356
+ const issueIcon = screen.getByLabelText('检查更新失败');
336
357
 
337
358
  expect(issueIcon.textContent).toBe('!');
338
359
  await user.hover(issueIcon);
339
360
 
340
361
  const tooltip = await screen.findByRole('tooltip');
341
- expect(tooltip.textContent).toContain('更新失败');
342
- expect(tooltip.textContent).toContain('runtime bundle sha256 mismatch');
362
+ expect(tooltip.textContent).toContain('检查更新失败');
363
+ expect(tooltip.textContent).toContain('fetch failed: getaddrinfo ENOTFOUND updates.nextclaw.io');
364
+ expect(tooltip.textContent).toContain('完整日志:nextclaw logs path');
343
365
  expect(screen.queryByText('更新被阻塞')).toBeNull();
344
366
  });
345
367
  });
@@ -14,7 +14,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/shar
14
14
  import { hostCapabilityManager } from '@/shared/lib/host-capabilities';
15
15
  import { t } from '@/shared/lib/i18n';
16
16
  import { cn } from '@/shared/lib/utils';
17
- import { ExternalLink } from 'lucide-react';
18
17
 
19
18
  type BrandHeaderProps = {
20
19
  className?: string;
@@ -122,19 +121,14 @@ function RuntimeUpdateInlineStatus({
122
121
  }
123
122
  const releaseNotesLink = resolveUpdateReleaseNotesLink(snapshot);
124
123
  if (snapshot.status === 'downloading') {
125
- return (
126
- <RuntimeUpdateInlineGroup>
127
- <RuntimeUpdateInlineBadge snapshot={snapshot} />
128
- {releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="amber" /> : null}
129
- </RuntimeUpdateInlineGroup>
130
- );
124
+ return <RuntimeUpdateInlineBadge snapshot={snapshot} releaseNotesLink={releaseNotesLink} />;
131
125
  }
132
126
  if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
133
127
  return <RuntimeUpdateInlineBadge snapshot={snapshot} />;
134
128
  }
135
129
  if (snapshot.status === 'downloaded') {
136
130
  return (
137
- <RuntimeUpdateInlineGroup>
131
+ <RuntimeUpdateReleaseNotesHover link={releaseNotesLink}>
138
132
  <button
139
133
  type="button"
140
134
  className="inline-flex h-5 shrink-0 items-center rounded-full bg-emerald-50 px-2 text-[11px] font-semibold leading-none text-emerald-700 ring-1 ring-emerald-100 transition-colors hover:bg-emerald-100 disabled:opacity-70"
@@ -143,13 +137,12 @@ function RuntimeUpdateInlineStatus({
143
137
  >
144
138
  {busyAction === 'applying' ? t('desktopUpdatesInlineApplying') : t('desktopUpdatesInlineReady')}
145
139
  </button>
146
- {releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="emerald" /> : null}
147
- </RuntimeUpdateInlineGroup>
140
+ </RuntimeUpdateReleaseNotesHover>
148
141
  );
149
142
  }
150
143
  if (snapshot.status === 'update-available') {
151
144
  return (
152
- <RuntimeUpdateInlineGroup>
145
+ <RuntimeUpdateReleaseNotesHover link={releaseNotesLink}>
153
146
  <button
154
147
  type="button"
155
148
  className="inline-flex h-5 shrink-0 items-center rounded-full bg-amber-50 px-2 text-[11px] font-semibold leading-none text-amber-700 ring-1 ring-amber-100 transition-colors hover:bg-amber-100 disabled:opacity-70"
@@ -158,51 +151,50 @@ function RuntimeUpdateInlineStatus({
158
151
  >
159
152
  {busyAction === 'downloading' ? t('desktopUpdatesInlineDownloading') : t('desktopUpdatesInlineDownload')}
160
153
  </button>
161
- {releaseNotesLink ? <RuntimeUpdateReleaseNotesButton link={releaseNotesLink} tone="amber" /> : null}
162
- </RuntimeUpdateInlineGroup>
154
+ </RuntimeUpdateReleaseNotesHover>
163
155
  );
164
156
  }
165
157
  return null;
166
158
  }
167
159
 
168
- function RuntimeUpdateInlineGroup({ children }: { children: ReactNode }) {
169
- return <span className="inline-flex shrink-0 items-center gap-1">{children}</span>;
170
- }
171
-
172
- function RuntimeUpdateReleaseNotesButton({
160
+ function RuntimeUpdateReleaseNotesHover({
161
+ children,
173
162
  link,
174
- tone,
175
163
  }: {
176
- link: ReleaseNotesLink;
177
- tone: 'amber' | 'emerald';
164
+ children: ReactNode;
165
+ link: ReleaseNotesLink | null;
178
166
  }) {
167
+ if (!link) {
168
+ return children;
169
+ }
179
170
  const label = t('desktopUpdatesUpdateReleaseNotesLabel').replace('{version}', link.versionLabel);
180
- const className = cn(
181
- 'inline-flex h-5 w-5 shrink-0 items-center justify-center rounded-full text-[11px] ring-1 transition-colors focus-visible:outline-none focus-visible:ring-2',
182
- tone === 'emerald'
183
- ? 'bg-emerald-50 text-emerald-700 ring-emerald-100 hover:bg-emerald-100 focus-visible:ring-emerald-300'
184
- : 'bg-amber-50 text-amber-700 ring-amber-100 hover:bg-amber-100 focus-visible:ring-amber-300'
185
- );
186
171
  return (
187
- <TooltipProvider delayDuration={250}>
188
- <Tooltip>
189
- <TooltipTrigger asChild>
190
- <button
191
- type="button"
192
- aria-label={label}
193
- className={className}
194
- onClick={() => void hostCapabilityManager.openExternalUrl(link.url)}
195
- >
196
- <ExternalLink className="h-3 w-3" aria-hidden="true" />
197
- </button>
198
- </TooltipTrigger>
199
- <TooltipContent side="bottom" className="text-xs">{label}</TooltipContent>
200
- </Tooltip>
201
- </TooltipProvider>
172
+ <span className="group/update-release-notes relative inline-flex shrink-0">
173
+ {children}
174
+ <span
175
+ data-update-release-notes-hover="true"
176
+ className="invisible pointer-events-none absolute left-1/2 top-full z-[var(--z-tooltip,10150)] -translate-x-1/2 pt-1 opacity-0 transition-opacity group-hover/update-release-notes:visible group-hover/update-release-notes:pointer-events-auto group-hover/update-release-notes:opacity-100 group-focus-within/update-release-notes:visible group-focus-within/update-release-notes:pointer-events-auto group-focus-within/update-release-notes:opacity-100"
177
+ >
178
+ <button
179
+ type="button"
180
+ aria-label={label}
181
+ className="whitespace-nowrap rounded-md border bg-popover px-3 py-1.5 text-left text-xs font-medium text-popover-foreground shadow-md outline-none transition-colors hover:bg-gray-100 focus-visible:ring-2 focus-visible:ring-gray-300"
182
+ onClick={() => void hostCapabilityManager.openExternalUrl(link.url)}
183
+ >
184
+ {label}
185
+ </button>
186
+ </span>
187
+ </span>
202
188
  );
203
189
  }
204
190
 
205
- function RuntimeUpdateInlineBadge({ snapshot }: { snapshot: UpdateSnapshot }) {
191
+ function RuntimeUpdateInlineBadge({
192
+ snapshot,
193
+ releaseNotesLink,
194
+ }: {
195
+ snapshot: UpdateSnapshot;
196
+ releaseNotesLink?: ReleaseNotesLink | null;
197
+ }) {
206
198
  if (snapshot.status === 'blocked' || snapshot.status === 'failed') {
207
199
  return <RuntimeUpdateIssueIcon snapshot={snapshot} />;
208
200
  }
@@ -211,23 +203,28 @@ function RuntimeUpdateInlineBadge({ snapshot }: { snapshot: UpdateSnapshot }) {
211
203
  return null;
212
204
  }
213
205
  return (
214
- <span
215
- className="inline-flex h-5 shrink-0 items-center rounded-full bg-amber-50 px-2 text-[11px] font-semibold leading-none text-amber-700 ring-1 ring-amber-100 transition-colors hover:bg-amber-100 disabled:opacity-70"
216
- title={t('updates')}
217
- >
218
- {label}
219
- </span>
206
+ <RuntimeUpdateReleaseNotesHover link={releaseNotesLink ?? null}>
207
+ <span
208
+ tabIndex={releaseNotesLink ? 0 : undefined}
209
+ className="inline-flex h-5 shrink-0 items-center rounded-full bg-amber-50 px-2 text-[11px] font-semibold leading-none text-amber-700 ring-1 ring-amber-100 outline-none transition-colors hover:bg-amber-100 focus-visible:ring-2 focus-visible:ring-amber-300"
210
+ >
211
+ {label}
212
+ </span>
213
+ </RuntimeUpdateReleaseNotesHover>
220
214
  );
221
215
  }
222
216
 
223
217
  function RuntimeUpdateIssueIcon({ snapshot }: { snapshot: UpdateSnapshot }) {
224
- const title = snapshot.status === 'failed' ? t('desktopUpdatesStatusFailed') : t('desktopUpdatesStatusBlocked');
218
+ const title = resolveUpdateIssueTitle(snapshot);
225
219
  const recoveryCommand = snapshot.recoveryCommand?.trim() || null;
220
+ const diagnosticCommand = snapshot.diagnosticCommand?.trim()
221
+ ? t('desktopUpdatesDiagnosticCommand').replace('{command}', snapshot.diagnosticCommand.trim())
222
+ : null;
226
223
  const diagnostic = snapshot.errorMessage?.trim() || snapshot.blockReason?.trim() || null;
227
224
  const rootCause = snapshot.status === 'blocked' && snapshot.blockReason
228
225
  ? t(`desktopUpdatesBlockedRootCause.${snapshot.blockReason}`)
229
226
  : null;
230
- const tooltip = [title, rootCause, diagnostic, recoveryCommand].filter(Boolean).join('\n');
227
+ const tooltip = [title, rootCause, diagnostic, recoveryCommand, diagnosticCommand].filter(Boolean).join('\n');
231
228
  return (
232
229
  <TooltipProvider delayDuration={250}>
233
230
  <Tooltip>
@@ -247,6 +244,13 @@ function RuntimeUpdateIssueIcon({ snapshot }: { snapshot: UpdateSnapshot }) {
247
244
  );
248
245
  }
249
246
 
247
+ function resolveUpdateIssueTitle(snapshot: UpdateSnapshot): string {
248
+ if (snapshot.status === 'blocked') {
249
+ return t('desktopUpdatesStatusBlocked');
250
+ }
251
+ return t(snapshot.failureStage ? `desktopUpdatesFailureStage.${snapshot.failureStage}` : 'desktopUpdatesStatusFailed');
252
+ }
253
+
250
254
  function resolveInlineDownloadLabel(snapshot: UpdateSnapshot): string {
251
255
  const percent = snapshot.progress?.percent;
252
256
  return percent === null || percent === undefined
@@ -216,7 +216,7 @@
216
216
  "chatContextReferenceParent": "Up one folder",
217
217
  "chatContextReferenceParentDescription": "Browse the parent folder without closing the reference menu.",
218
218
  "chatContextReferenceParentHint": "Press Enter to open the parent folder.",
219
- "chatContextReferenceCurrentDirectory": "Current folder",
219
+ "chatContextReferenceCurrentDirectory": "Reference current folder",
220
220
  "chatContextReferenceFileDescription": "Use this file as explicit context for the current message.",
221
221
  "chatContextReferenceDirectoryDescription": "Use this folder as a context scope for the current message.",
222
222
  "chatContextReferenceProjectRoot": "Project directory",
@@ -37,6 +37,10 @@
37
37
  "desktopUpdatesStatusUpToDate": "Up to Date",
38
38
  "desktopUpdatesStatusBlocked": "Blocked",
39
39
  "desktopUpdatesStatusFailed": "Failed",
40
+ "desktopUpdatesFailureStage.check": "Update Check Failed",
41
+ "desktopUpdatesFailureStage.download": "Update Download Failed",
42
+ "desktopUpdatesFailureStage.apply": "Update Apply Failed",
43
+ "desktopUpdatesDiagnosticCommand": "Full logs: {command}",
40
44
  "desktopUpdatesInlineDownload": "Download",
41
45
  "desktopUpdatesInlineDownloading": "Downloading",
42
46
  "desktopUpdatesInlineDownloadingPercent": "{percent}%",
@@ -216,7 +216,7 @@
216
216
  "chatContextReferenceParent": "返回上一级",
217
217
  "chatContextReferenceParentDescription": "在不关闭引用菜单的情况下浏览上一级文件夹。",
218
218
  "chatContextReferenceParentHint": "回车打开上一级文件夹。",
219
- "chatContextReferenceCurrentDirectory": "当前文件夹",
219
+ "chatContextReferenceCurrentDirectory": "引用当前文件夹",
220
220
  "chatContextReferenceFileDescription": "把这个文件作为本次消息的显式上下文。",
221
221
  "chatContextReferenceDirectoryDescription": "把这个目录作为本次消息的上下文范围。",
222
222
  "chatContextReferenceProjectRoot": "项目目录",
@@ -37,6 +37,10 @@
37
37
  "desktopUpdatesStatusUpToDate": "已是最新",
38
38
  "desktopUpdatesStatusBlocked": "更新被阻塞",
39
39
  "desktopUpdatesStatusFailed": "更新失败",
40
+ "desktopUpdatesFailureStage.check": "检查更新失败",
41
+ "desktopUpdatesFailureStage.download": "下载更新失败",
42
+ "desktopUpdatesFailureStage.apply": "应用更新失败",
43
+ "desktopUpdatesDiagnosticCommand": "完整日志:{command}",
40
44
  "desktopUpdatesInlineDownload": "下载",
41
45
  "desktopUpdatesInlineDownloading": "下载中",
42
46
  "desktopUpdatesInlineDownloadingPercent": "下载 {percent}%",
@@ -1 +0,0 @@
1
- import{a as e,n as t,p as n,r,u as i}from"./react-Io6Pfq8D.js";import{Et as a,Gt as o,Jt as s,Kt as c,Ot as l,Qt as u,St as d,Tt as f,Xt as p,Yt as m,Zt as h,en as g,kt as _,on as v,qt as y,wt as b}from"./api-V8m62-13.js";import{a as x,i as S,n as C,r as w,t as T}from"./select-BiOpo2kV.js";import{t as E}from"./download-CdO6NoHj.js";import{t as D}from"./external-link-C5NFoh5I.js";import{t as O}from"./refresh-cw-C6j2tjul.js";import{t as k}from"./rotate-cw-Bv2B6fAV.js";import{t as A}from"./host-capabilities-CFbAfrrA.js";var j=n(),M={checking:`desktopUpdatesStatusChecking`,"update-available":`desktopUpdatesStatusAvailable`,downloading:`desktopUpdatesStatusDownloading`,downloaded:`desktopUpdatesStatusDownloaded`,"up-to-date":`desktopUpdatesStatusUpToDate`,blocked:`desktopUpdatesStatusBlocked`,failed:`desktopUpdatesStatusFailed`},N={feature:`desktopUpdatesReleaseNotesFeature`,enhancement:`desktopUpdatesReleaseNotesEnhancement`,fix:`desktopUpdatesReleaseNotesFix`,compatibility:`desktopUpdatesReleaseNotesCompatibility`};function P({status:e}){return(0,j.jsx)(`span`,{className:i(`inline-flex rounded-full px-3 py-1 text-xs font-medium ring-1`,W(e)),children:U(e)})}function F({label:e,value:t}){return(0,j.jsxs)(`div`,{className:`rounded-xl border border-gray-200 bg-gray-50/60 p-4`,children:[(0,j.jsx)(`p`,{className:`text-xs font-medium uppercase tracking-[0.08em] text-gray-500`,children:e}),(0,j.jsx)(`p`,{className:`mt-2 text-base font-semibold text-gray-900`,children:t})]})}function I({snapshot:e}){if(e.status!==`downloading`)return null;let t=e.progress?.percent,n=t==null?r(`desktopUpdatesDownloadProgressUnknown`):r(`desktopUpdatesDownloadProgressPercent`).replace(`{percent}`,String(t)),i=B(e.progress?.downloadedBytes??0,e.progress?.totalBytes??null);return(0,j.jsxs)(`div`,{className:`rounded-2xl border border-amber-200 bg-amber-50/70 p-4`,children:[(0,j.jsxs)(`div`,{className:`flex items-center justify-between gap-4`,children:[(0,j.jsx)(`p`,{className:`text-sm font-semibold text-amber-800`,children:n}),(0,j.jsx)(`p`,{className:`text-xs font-medium text-amber-700`,children:i})]}),(0,j.jsx)(`div`,{className:`mt-3 h-2 overflow-hidden rounded-full bg-amber-100`,children:(0,j.jsx)(`div`,{className:`h-full rounded-full bg-amber-500 transition-[width]`,style:{width:`${t??0}%`}})})]})}function L({label:e,help:t,checked:n,disabled:r,onCheckedChange:i}){return(0,j.jsxs)(`div`,{className:`flex items-start justify-between gap-4 rounded-xl border border-gray-200 p-4`,children:[(0,j.jsxs)(`div`,{className:`space-y-1`,children:[(0,j.jsx)(u,{children:e}),(0,j.jsx)(`p`,{className:`text-sm text-gray-500`,children:t})]}),(0,j.jsx)(d,{checked:n,disabled:r,onCheckedChange:i})]})}function R(e){return e?.trim()||`-`}function z(e){return e?t(e):`-`}function B(e,t){let n=V(e);return t&&t>0?`${n} / ${V(t)}`:n}function V(e){if(!Number.isFinite(e)||e<=0)return`0 B`;let t=[`B`,`KB`,`MB`,`GB`],n=e,r=0;for(;n>=1024&&r<t.length-1;)n/=1024,r+=1;return`${n>=10||r===0?n.toFixed(0):n.toFixed(1)} ${t[r]}`}function H(e){return r(e===`beta`?`desktopUpdatesChannelBeta`:`desktopUpdatesChannelStable`)}function U(e){return r(M[e]??`desktopUpdatesStatusIdle`)}function W(e){return e===`downloaded`?`bg-emerald-50 text-emerald-700 ring-emerald-100`:e===`update-available`||e===`downloading`||e===`checking`?`bg-amber-50 text-amber-700 ring-amber-100`:e===`failed`||e===`blocked`?`bg-red-50 text-red-700 ring-red-100`:`bg-gray-100 text-gray-700 ring-gray-200`}function G(){return e()===`zh`?`zh-CN`:`en-US`}function K(e,t){return f(e.title,t)??r(N[e.kind]??`desktopUpdatesReleaseNotesEnhancement`)}function q({snapshot:e}){let t=a(e),n=G(),i=v({queryKey:[`runtime-release-notes`,t],queryFn:async()=>await b(t??``),enabled:!!t,retry:!1,staleTime:300*1e3}),l=i.data,u=f(l?.title,n)??r(`desktopUpdatesReleaseNotesPreviewTitle`),d=f(l?.summary,n)??r(`desktopUpdatesReleaseNotesPreviewDescription`);return e.releaseNotesUrl?(0,j.jsxs)(o,{children:[(0,j.jsx)(s,{children:(0,j.jsxs)(`div`,{className:`flex flex-wrap items-start justify-between gap-3`,children:[(0,j.jsxs)(`div`,{className:`space-y-1`,children:[(0,j.jsx)(m,{children:u}),(0,j.jsx)(y,{children:d})]}),(0,j.jsxs)(g,{variant:`ghost`,onClick:()=>void A.openExternalUrl(e.releaseNotesUrl??``),children:[(0,j.jsx)(D,{className:`mr-2 h-4 w-4`}),r(`desktopUpdatesReleaseNotes`)]})]})}),(0,j.jsxs)(c,{className:`space-y-4`,children:[i.isLoading?(0,j.jsx)(`p`,{className:`text-sm text-gray-500`,children:r(`desktopUpdatesReleaseNotesPreviewLoading`)}):null,!t||i.isError?(0,j.jsx)(`p`,{className:`text-sm text-gray-500`,children:r(`desktopUpdatesReleaseNotesPreviewUnavailable`)}):null,l?(0,j.jsx)(`div`,{className:`grid gap-4 md:grid-cols-2`,children:l.sections.map(e=>(0,j.jsxs)(`div`,{className:`rounded-xl border border-gray-200 bg-gray-50/60 p-4`,children:[(0,j.jsx)(`p`,{className:`text-sm font-semibold text-gray-900`,children:K(e,n)}),(0,j.jsx)(`ul`,{className:`mt-3 space-y-3`,children:e.items.map((t,r)=>{let i=f(t.title,n),a=f(t.body,n);return(0,j.jsxs)(`li`,{className:`space-y-1`,children:[i?(0,j.jsx)(`p`,{className:`text-sm font-medium text-gray-800`,children:i}):null,a?(0,j.jsx)(`p`,{className:`text-sm leading-5 text-gray-600`,children:a}):null]},`${i??e.kind}-${r}`)})})]},e.kind))}):null]})]}):null}function J(){return(0,j.jsxs)(h,{className:`space-y-6`,children:[(0,j.jsx)(p,{title:r(`runtimeUpdatesPageTitle`),description:r(`runtimeUpdatesPageDescription`)}),(0,j.jsxs)(o,{children:[(0,j.jsxs)(s,{children:[(0,j.jsx)(m,{children:r(`runtimeUpdatesUnavailableTitle`)}),(0,j.jsx)(y,{children:r(`runtimeUpdatesUnavailableDescription`)})]}),(0,j.jsx)(c,{children:(0,j.jsx)(`p`,{className:`text-sm text-gray-500`,children:r(`runtimeUpdatesUnavailableHint`)})})]})]})}function Y(){let{supported:e,initialized:t,busyAction:n,snapshot:a}=_();if(!t)return(0,j.jsx)(`div`,{className:`p-8 text-gray-400`,children:r(`loading`)});if(!e||!a)return(0,j.jsx)(J,{});let d=n===`checking`,f=n===`downloading`,v=n===`applying`,b=n===`saving-preferences`,D=n===`switching-channel`,A=a.status===`update-available`&&!f&&!v,M=a.status===`downloaded`&&!v,N=[[r(`runtimeUpdatesHostVersion`),R(a.hostVersion)],[r(`desktopUpdatesCurrentBundleVersion`),R(a.currentVersion)],[r(`desktopUpdatesAvailableVersion`),R(a.availableVersion)],[r(`desktopUpdatesLastCheckedAt`),z(a.lastCheckedAt)],[r(`desktopUpdatesCurrentChannel`),H(a.channel)]];return(0,j.jsxs)(h,{className:`space-y-6`,children:[(0,j.jsx)(p,{title:r(`runtimeUpdatesPageTitle`),description:r(`runtimeUpdatesPageDescription`),actions:(0,j.jsxs)(g,{variant:`outline`,onClick:()=>void l.checkForUpdates(),disabled:d||f||v,children:[(0,j.jsx)(O,{className:i(`mr-2 h-4 w-4`,d&&`animate-spin`)}),r(`desktopUpdatesCheckNow`)]})}),(0,j.jsxs)(o,{children:[(0,j.jsxs)(s,{children:[(0,j.jsx)(m,{children:r(`desktopUpdatesOverviewTitle`)}),(0,j.jsx)(y,{children:r(`desktopUpdatesOverviewDescription`)})]}),(0,j.jsxs)(c,{className:`space-y-5`,children:[(0,j.jsxs)(`div`,{className:`flex flex-wrap items-center gap-3`,children:[(0,j.jsx)(`span`,{className:`text-sm font-medium text-gray-700`,children:r(`desktopUpdatesStatusLabel`)}),(0,j.jsx)(P,{status:a.status})]}),(0,j.jsx)(`div`,{className:`grid gap-4 md:grid-cols-2 xl:grid-cols-5`,children:N.map(([e,t])=>(0,j.jsx)(F,{label:e,value:t},e))}),a.channel===`beta`?(0,j.jsxs)(`div`,{className:`rounded-2xl border border-amber-200 bg-amber-50/70 p-4`,children:[(0,j.jsx)(`p`,{className:`text-sm font-semibold text-amber-800`,children:r(`desktopUpdatesBetaBadgeTitle`)}),(0,j.jsx)(`p`,{className:`mt-1 text-sm text-amber-700`,children:r(`desktopUpdatesBetaBadgeDescription`)})]}):null,a.downloadedVersion?(0,j.jsxs)(`div`,{className:`rounded-2xl border border-emerald-200 bg-emerald-50/70 p-4`,children:[(0,j.jsx)(`p`,{className:`text-sm font-semibold text-emerald-800`,children:r(`desktopUpdatesDownloadedBannerTitle`)}),(0,j.jsx)(`p`,{className:`mt-1 text-sm text-emerald-700`,children:r(`runtimeUpdatesDownloadedBannerDescription`).replace(`{version}`,a.downloadedVersion)})]}):null,(0,j.jsx)(I,{snapshot:a}),a.status===`blocked`?(0,j.jsxs)(`div`,{className:`rounded-2xl border border-red-200 bg-red-50/70 p-4`,children:[(0,j.jsx)(`p`,{className:`text-sm font-semibold text-red-800`,children:r(`desktopUpdatesBlockedTitle`)}),(0,j.jsx)(`p`,{className:`mt-1 text-sm text-red-700`,children:a.errorMessage??r(`desktopUpdatesBlockedDescription`)}),a.recoveryCommand?(0,j.jsx)(`code`,{className:`mt-3 block rounded-lg bg-white/70 px-3 py-2 text-xs text-red-800`,children:a.recoveryCommand}):null]}):null,a.errorMessage&&a.status!==`blocked`?(0,j.jsx)(`div`,{className:`rounded-2xl border border-red-200 bg-red-50/70 p-4 text-sm text-red-700`,children:a.errorMessage}):null]})]}),(0,j.jsx)(q,{snapshot:a}),(0,j.jsxs)(o,{children:[(0,j.jsxs)(s,{children:[(0,j.jsx)(m,{children:r(`desktopUpdatesPreferencesTitle`)}),(0,j.jsx)(y,{children:r(`desktopUpdatesPreferencesDescription`)})]}),(0,j.jsxs)(c,{className:`space-y-5`,children:[(0,j.jsx)(`div`,{className:`rounded-xl border border-gray-200 p-4`,children:(0,j.jsxs)(`div`,{className:`space-y-3`,children:[(0,j.jsxs)(`div`,{className:`space-y-1`,children:[(0,j.jsx)(u,{children:r(`desktopUpdatesReleaseChannel`)}),(0,j.jsx)(`p`,{className:`text-sm text-gray-500`,children:r(`desktopUpdatesReleaseChannelHelp`)})]}),(0,j.jsxs)(T,{value:a.channel,disabled:D||d||f||v,onValueChange:e=>void l.updateChannel(e),children:[(0,j.jsx)(S,{className:`w-full max-w-sm`,children:(0,j.jsx)(x,{placeholder:r(`desktopUpdatesReleaseChannel`)})}),(0,j.jsxs)(C,{children:[(0,j.jsx)(w,{value:`stable`,children:r(`desktopUpdatesChannelStable`)}),(0,j.jsx)(w,{value:`beta`,children:r(`desktopUpdatesChannelBeta`)})]})]}),(0,j.jsx)(`p`,{className:`text-sm text-gray-500`,children:r(`desktopUpdatesReleaseChannelDowngradeHint`)})]})}),(0,j.jsx)(L,{label:r(`desktopUpdatesAutomaticChecks`),help:r(`desktopUpdatesAutomaticChecksHelp`),checked:a.preferences.automaticChecks,disabled:b||D,onCheckedChange:e=>void l.updatePreferences({automaticChecks:e})}),(0,j.jsx)(L,{label:r(`desktopUpdatesAutoDownload`),help:r(`desktopUpdatesAutoDownloadHelp`),checked:a.preferences.autoDownload,disabled:b||D,onCheckedChange:e=>void l.updatePreferences({autoDownload:e})})]})]}),(0,j.jsxs)(o,{children:[(0,j.jsxs)(s,{children:[(0,j.jsx)(m,{children:r(`desktopUpdatesActionsTitle`)}),(0,j.jsx)(y,{children:r(`runtimeUpdatesActionsDescription`)})]}),(0,j.jsxs)(c,{className:`flex flex-wrap items-center gap-3`,children:[(0,j.jsxs)(g,{variant:`outline`,onClick:()=>void l.checkForUpdates(),disabled:d||f||v,children:[(0,j.jsx)(O,{className:i(`mr-2 h-4 w-4`,d&&`animate-spin`)}),r(`desktopUpdatesCheckNow`)]}),(0,j.jsxs)(g,{onClick:()=>void l.downloadUpdate(),disabled:!A,children:[(0,j.jsx)(E,{className:i(`mr-2 h-4 w-4`,f&&`animate-bounce`)}),r(`desktopUpdatesDownloadNow`)]}),(0,j.jsxs)(g,{variant:`secondary`,onClick:()=>void l.applyDownloadedUpdate(),disabled:!M,children:[(0,j.jsx)(k,{className:i(`mr-2 h-4 w-4`,v&&`animate-spin`)}),r(`runtimeUpdatesApplyNow`)]})]})]})]})}export{Y as DesktopUpdateConfig};
@@ -1 +0,0 @@
1
- import{t as e}from"./doc-browser-B1FXvZm6.js";export{e as DocBrowser};