@allbluecn/web-app 0.27.2 → 0.27.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@allbluecn/web-app",
3
- "version": "0.27.2",
3
+ "version": "0.27.3",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "files": [
@@ -18,6 +18,8 @@ let captured: CaptureCb | null = null
18
18
  const unsubscribe = vi.fn()
19
19
  let noBridge = false
20
20
 
21
+ const routerInvalidate = vi.hoisted(() => vi.fn())
22
+
21
23
  vi.mock('@/lib/desktop-bridge-types', () => ({
22
24
  getDesktopBridge: () => {
23
25
  if (noBridge) return null
@@ -30,6 +32,11 @@ vi.mock('@/lib/desktop-bridge-types', () => ({
30
32
  },
31
33
  }))
32
34
 
35
+ // 需求列表/看板走 route loader(非 react-query):事件需同时 router.invalidate
36
+ vi.mock('@tanstack/react-router', () => ({
37
+ useRouter: () => ({ invalidate: routerInvalidate }),
38
+ }))
39
+
33
40
  import { QuickCaptureInvalidator } from '../quick-capture-invalidate'
34
41
 
35
42
  let client: QueryClient
@@ -61,7 +68,7 @@ describe('QuickCaptureInvalidator 主窗跨窗口失效', () => {
61
68
  expect(captured).toBeTypeOf('function')
62
69
  })
63
70
 
64
- it('桥推送记录创建事件 → invalidateQueries 被调(全量失效,无 queryKey 过滤参数)', async () => {
71
+ it('桥推送记录创建事件 → invalidateQueries 全量失效 + router.invalidate 重跑 loader', async () => {
65
72
  mount()
66
73
  const invalidateSpy = vi.spyOn(client, 'invalidateQueries')
67
74
  await act(async () => {
@@ -71,6 +78,9 @@ describe('QuickCaptureInvalidator 主窗跨窗口失效', () => {
71
78
  expect(invalidateSpy).toHaveBeenCalledTimes(1)
72
79
  // 全量失效口径(复审 C2):无 queryKey 过滤参数——零参调用
73
80
  expect(invalidateSpy).toHaveBeenCalledWith()
81
+ // 列表数据走 route loader(非 react-query):必须同时重跑 loader,
82
+ // 否则新记录不会出现在需求列表(用户会以为要手动刷新)
83
+ expect(routerInvalidate).toHaveBeenCalledTimes(1)
74
84
  })
75
85
 
76
86
  it('desktopBridge 为 undefined(浏览器直开)→ 挂载不抛错、零订阅', () => {
@@ -4,24 +4,30 @@
4
4
 
5
5
  // 主窗跨窗口失效(托盘 mini 快捷记录面板闭环第 4 步):订阅
6
6
  // desktopBridge.onQuickCaptureCreated——mini 面板保存成功 → Electron main 广播
7
- // → 本组件失效 react-query 缓存。失效口径(复审 C2):invalidateQueries() 全量
8
- // 失效(无 queryKey 过滤参数)——覆盖 projects-brief/workspaces-brief/story
9
- // 视图;低频事件成本可接受,不做精确 key 白名单。旧壳(无此方法)undefined、
10
- // 浏览器直开(无桥)null——可选链双保险优雅降级,挂载零成本零报错。
7
+ // → 本组件刷新主窗数据。两条刷新通道缺一不可:
8
+ // ① invalidateQueries() 全量失效(复审 C2 口径)——覆盖 projects-brief /
9
+ // workspaces-brief / 通知等 react-query 缓存(灵感箱首建后侧边栏即时出现);
10
+ // ② router.invalidate() 重跑 active route loader——需求列表/看板/日历的数据
11
+ // 走 route loader + 组件内 reload 原语(非 react-query),仅失效 query 缓存
12
+ // 不会让新记录出现在列表里(用户会以为「没保存,要手动刷新」)。
13
+ // 旧壳(无此方法)undefined、浏览器直开(无桥)null——可选链双保险优雅降级。
11
14
  // 挂载点:providers(Providers 全页挂载,DesktopRenamedToast 旁)。
12
15
  import { useEffect } from "react"
13
16
  import { useQueryClient } from "@tanstack/react-query"
17
+ import { useRouter } from "@tanstack/react-router"
14
18
  import { getDesktopBridge } from "@/lib/desktop-bridge-types"
15
19
 
16
20
  export function QuickCaptureInvalidator() {
17
21
  const queryClient = useQueryClient()
22
+ const router = useRouter()
18
23
 
19
24
  useEffect(() => {
20
25
  const off = getDesktopBridge()?.onQuickCaptureCreated?.(() => {
21
26
  void queryClient.invalidateQueries()
27
+ void router.invalidate()
22
28
  })
23
29
  return off
24
- }, [queryClient])
30
+ }, [queryClient, router])
25
31
 
26
32
  return null
27
33
  }
@@ -55,6 +55,8 @@ const bridgeStub = vi.hoisted(() => {
55
55
  notifyQuickCapture: vi.fn(),
56
56
  setMiniPanelPinned: vi.fn(),
57
57
  popUpTrayMenu: vi.fn(),
58
+ openMainWindow: vi.fn(),
59
+ quitApp: vi.fn(),
58
60
  onMiniPanelMode: vi.fn((_cb: (mode: string) => void) => unsubMiniPanelMode),
59
61
  onMiniPanelPinned: vi.fn((_cb: (pinned: boolean) => void) => unsubMiniPanelPinned),
60
62
  }
@@ -317,11 +319,28 @@ describe('QuickCapturePanel', () => {
317
319
  )
318
320
  })
319
321
 
320
- it('设置按钮:请求弹出托盘菜单(与菜单栏右键同一份)', async () => {
322
+ it('设置菜单在面板处打开(内容与托盘右键一致):打开 AllBlue / 快捷记录灵感 / 退出', async () => {
321
323
  const user = userEvent.setup()
322
324
  renderPanel()
325
+ await screen.findByText('独立需求')
326
+ await user.click(screen.getByRole('button', { name: '设置' }))
327
+ // 面板内嵌菜单(不弹原生托盘菜单)
328
+ expect(await screen.findByText('打开 AllBlue')).toBeTruthy()
329
+ expect(screen.getByText('快捷记录需求')).toBeTruthy()
330
+ expect(screen.getByText('快捷记录灵感')).toBeTruthy()
331
+ expect(screen.getByText('退出 AllBlue')).toBeTruthy()
332
+ expect(bridgeStub.popUpTrayMenu).not.toHaveBeenCalled()
333
+ // 快捷记录灵感 → 面板切到灵感箱
334
+ await user.click(screen.getByText('快捷记录灵感'))
335
+ await waitFor(() => expect(screen.getByRole('button', { name: /灵感箱/ })).toBeTruthy())
336
+ // 打开主窗
337
+ await user.click(screen.getByRole('button', { name: '设置' }))
338
+ await user.click(await screen.findByText('打开 AllBlue'))
339
+ expect(bridgeStub.openMainWindow).toHaveBeenCalledTimes(1)
340
+ // 退出
323
341
  await user.click(screen.getByRole('button', { name: '设置' }))
324
- expect(bridgeStub.popUpTrayMenu).toHaveBeenCalledTimes(1)
342
+ await user.click(await screen.findByText('退出 AllBlue'))
343
+ expect(bridgeStub.quitApp).toHaveBeenCalledTimes(1)
325
344
  })
326
345
 
327
346
  it('标题字号做 md 适配;编辑器收到 hideMobileToolbar(隐藏窄窗底部工具栏)', async () => {
@@ -44,7 +44,13 @@ import { listProjectsBriefFn } from "@/server/serverFns/project"
44
44
  import { Button } from "@/components/ui/button"
45
45
  import { Input } from "@/components/ui/input"
46
46
  import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"
47
- import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"
47
+ import {
48
+ DropdownMenu,
49
+ DropdownMenuContent,
50
+ DropdownMenuItem,
51
+ DropdownMenuSeparator,
52
+ DropdownMenuTrigger,
53
+ } from "@/components/ui/dropdown-menu"
48
54
  import { toast } from "@/components/ui/toast"
49
55
  import { LucideIconByName } from "@/components/shared-kit/icons/lucide-icon-picker/lucide-icon-by-name"
50
56
  import { NotionDocEditor } from "@/components/tiptap/notion-like"
@@ -98,6 +104,11 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
98
104
  const [aiGenerating, setAiGenerating] = useState(false)
99
105
  const [isEditorFocused, setIsEditorFocused] = useState(false)
100
106
  const [pinned, setPinned] = useState(false)
107
+ // SSR/CSR 首帧一致性:AI 工具栏含 useToolbarOverflow 测量层(客户端首帧多一层
108
+ // DOM),直接 SSR 会 hydration mismatch(React 整树重建 → 输入框事件重绑,
109
+ // 表现为 Enter/Esc 时好时坏)。挂载后再渲染工具栏。
110
+ const [mounted, setMounted] = useState(false)
111
+ const [appVersion, setAppVersion] = useState("")
101
112
 
102
113
  const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
103
114
  // 保存防重:请求在途(标题尚未清空)期间忽略再次提交,防重复落库
@@ -170,6 +181,21 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
170
181
  return () => unsubscribe?.()
171
182
  }, [])
172
183
 
184
+ // 首帧一致性 + 版本号(设置菜单展示;旧壳无 appInfo 时留空)
185
+ useEffect(() => {
186
+ setMounted(true)
187
+ }, [])
188
+
189
+ useEffect(() => {
190
+ const bridge = getDesktopBridge()
191
+ void bridge
192
+ ?.appInfo?.()
193
+ .then((info) => {
194
+ if (info?.version) setAppVersion(info.version)
195
+ })
196
+ .catch(() => {})
197
+ }, [])
198
+
173
199
  // 反馈定时器 unmount 清理(组件销毁后不再 setState,防泄漏)
174
200
  useEffect(() => {
175
201
  return () => {
@@ -301,40 +327,63 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
301
327
  </span>
302
328
  </div>
303
329
  <div className="flex shrink-0 items-center gap-0.5">
304
- <Tooltip>
305
- <TooltipTrigger
306
- render={
307
- <Button
308
- type="button"
309
- variant="ghost"
310
- size="icon-sm"
311
- aria-label={m.quick_capture_pin()}
312
- aria-pressed={pinned}
313
- className={pinned ? "text-primary" : undefined}
314
- onClick={togglePinned}
315
- />
316
- }
317
- >
318
- {pinned ? <PinOff className="size-4" /> : <Pin className="size-4" />}
319
- </TooltipTrigger>
320
- <TooltipContent>{m.quick_capture_pin()}</TooltipContent>
321
- </Tooltip>
322
- <Tooltip>
323
- <TooltipTrigger
330
+ <Button
331
+ type="button"
332
+ variant="ghost"
333
+ size="icon-sm"
334
+ aria-label={m.quick_capture_pin()}
335
+ aria-pressed={pinned}
336
+ className={pinned ? "text-primary" : undefined}
337
+ onClick={togglePinned}
338
+ >
339
+ {pinned ? <PinOff className="size-4" /> : <Pin className="size-4" />}
340
+ </Button>
341
+ <DropdownMenu>
342
+ <DropdownMenuTrigger
324
343
  render={
325
344
  <Button
326
345
  type="button"
327
346
  variant="ghost"
328
347
  size="icon-sm"
329
348
  aria-label={m.quick_capture_settings()}
330
- onClick={() => getDesktopBridge()?.popUpTrayMenu?.()}
331
349
  />
332
350
  }
333
351
  >
334
352
  <Settings className="size-4" />
335
- </TooltipTrigger>
336
- <TooltipContent>{m.quick_capture_settings()}</TooltipContent>
337
- </Tooltip>
353
+ </DropdownMenuTrigger>
354
+ {/* 设置菜单在面板处弹出,内容与菜单栏图标右键菜单一致(打开/快捷记录/版本/退出) */}
355
+ <DropdownMenuContent align="end" className="w-48">
356
+ <DropdownMenuItem onClick={() => getDesktopBridge()?.openMainWindow?.()}>
357
+ {m.quick_capture_openMain()}
358
+ </DropdownMenuItem>
359
+ <DropdownMenuItem
360
+ onClick={() => {
361
+ const pick = pickForMode("story")
362
+ if (pick) handlePickProject(pick)
363
+ }}
364
+ >
365
+ {m.quick_capture_quickStory()}
366
+ </DropdownMenuItem>
367
+ <DropdownMenuItem
368
+ onClick={() => {
369
+ const pick = pickForMode("inspiration")
370
+ if (pick) handlePickProject(pick)
371
+ }}
372
+ >
373
+ {m.quick_capture_quickIdea()}
374
+ </DropdownMenuItem>
375
+ {appVersion ? (
376
+ <>
377
+ <DropdownMenuSeparator />
378
+ <DropdownMenuItem disabled>{`v${appVersion}`}</DropdownMenuItem>
379
+ </>
380
+ ) : null}
381
+ <DropdownMenuSeparator />
382
+ <DropdownMenuItem onClick={() => getDesktopBridge()?.quitApp?.()}>
383
+ {m.quick_capture_quit()}
384
+ </DropdownMenuItem>
385
+ </DropdownMenuContent>
386
+ </DropdownMenu>
338
387
  </div>
339
388
  </header>
340
389
 
@@ -438,16 +487,20 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
438
487
  </div>
439
488
 
440
489
  <div className="mr-2 mb-1 flex shrink-0 items-center justify-end gap-1">
441
- <StoryAiToolbar
442
- description={description.markdown}
443
- title={name}
444
- mode="editor"
445
- editorRef={tiptapEditorRef}
446
- model={aiModel}
447
- groups={aiGroups}
448
- onModelChange={setAiModel}
449
- onGenerating={setAiGenerating}
450
- />
490
+ {/* 挂载后才渲染:工具栏测量层(客户端首帧多一层 DOM)会造成 hydration
491
+ mismatch → React 整树重建 → 输入框事件重绑(快捷键时好时坏) */}
492
+ {mounted && (
493
+ <StoryAiToolbar
494
+ description={description.markdown}
495
+ title={name}
496
+ mode="editor"
497
+ editorRef={tiptapEditorRef}
498
+ model={aiModel}
499
+ groups={aiGroups}
500
+ onModelChange={setAiModel}
501
+ onGenerating={setAiGenerating}
502
+ />
503
+ )}
451
504
  </div>
452
505
  </div>
453
506
 
@@ -51,6 +51,8 @@ async function executeFailingMutation(
51
51
 
52
52
  beforeEach(() => {
53
53
  vi.mocked(toast.error).mockClear()
54
+ // 取证钩子(handleGlobalError 内 console.error 打印具体错误)不污染测试输出
55
+ vi.spyOn(console, 'error').mockImplementation(() => {})
54
56
  })
55
57
 
56
58
  describe('QueryCache 全局 onError(web)', () => {
@@ -112,7 +112,7 @@ export const SDK_GROUPS: SdkGroup[] = [
112
112
  "items": [
113
113
  {
114
114
  "name": "@allbluecn/web-app",
115
- "version": "v0.27.2",
115
+ "version": "v0.27.3",
116
116
  "lastUpdate": "Nov 5, 2026",
117
117
  "source": "apps/web/package.json",
118
118
  "docsUrl": "https://github.com/allbluecn/allblue"
@@ -110,6 +110,12 @@ export interface DesktopBridgeApi {
110
110
  setMiniPanelPinned?: (pinned: boolean) => void
111
111
  onMiniPanelPinned?: (cb: (pinned: boolean) => void) => () => void
112
112
  popUpTrayMenu?: () => void
113
+ /** mini 面板设置菜单(2026-11-05 追加,add-only):
114
+ * - openMainWindow:打开/前置主窗(菜单项「打开 AllBlue」);
115
+ * - quitApp:退出应用(菜单项「退出 AllBlue」,复用 before-quit 清理链)。
116
+ * 旧壳 undefined——调用方可选链降级。 */
117
+ openMainWindow?: () => void
118
+ quitApp?: () => void
113
119
  }
114
120
 
115
121
  /** A11 desktop-state 三态载荷(main 侧 desktop-state.json 持久化态的广播形状):
@@ -69,6 +69,9 @@ function handleGlobalError(
69
69
  if (isGlobalToastSuppressed(error, meta)) return
70
70
  // toast 仅浏览器端有渲染通道,SSR 静默
71
71
  if (typeof window === 'undefined') return
72
+ // 取证钩子:toast 只给通用文案,具体错误落 console 便于用户/排查时定位
73
+ // (哪个 serverFn 失败、状态码、消息;不影响 toast 行为)
74
+ console.error('[query-global-error]', error)
72
75
  if (isClientErrorStatus(extractErrorStatus(error))) {
73
76
  // 4xx(非 401):业务语义错误,展示详情;空消息回落通用文案
74
77
  toast.error(pickErrorMessage(error, new WeakSet()) ?? m.common_error_generic())
@@ -3550,8 +3550,12 @@ export * from './prototypepanel_title1.js'
3550
3550
  export * from './quick_capture_failed.js'
3551
3551
  export * from './quick_capture_hint.js'
3552
3552
  export * from './quick_capture_inbox.js'
3553
+ export * from './quick_capture_openmain1.js'
3553
3554
  export * from './quick_capture_paneltitle1.js'
3554
3555
  export * from './quick_capture_pin.js'
3556
+ export * from './quick_capture_quickidea1.js'
3557
+ export * from './quick_capture_quickstory1.js'
3558
+ export * from './quick_capture_quit.js'
3555
3559
  export * from './quick_capture_saved.js'
3556
3560
  export * from './quick_capture_settings.js'
3557
3561
  export * from './quick_capture_submit.js'
@@ -0,0 +1,30 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Quick_Capture_Openmain1Inputs */
7
+
8
+ const zh_quick_capture_openmain1 = /** @type {(inputs: Quick_Capture_Openmain1Inputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`打开 AllBlue`)
10
+ };
11
+
12
+ const en_quick_capture_openmain1 = /** @type {(inputs: Quick_Capture_Openmain1Inputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Open AllBlue`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Open AllBlue" |
20
+ *
21
+ * @param {Quick_Capture_Openmain1Inputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ const quick_capture_openmain1 = /** @type {((inputs?: Quick_Capture_Openmain1Inputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Quick_Capture_Openmain1Inputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_quick_capture_openmain1(inputs)
28
+ return zh_quick_capture_openmain1(inputs)
29
+ });
30
+ export { quick_capture_openmain1 as "quick_capture_openMain" }
@@ -0,0 +1,30 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Quick_Capture_Quickidea1Inputs */
7
+
8
+ const zh_quick_capture_quickidea1 = /** @type {(inputs: Quick_Capture_Quickidea1Inputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`快捷记录灵感`)
10
+ };
11
+
12
+ const en_quick_capture_quickidea1 = /** @type {(inputs: Quick_Capture_Quickidea1Inputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Quick Idea`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Quick Idea" |
20
+ *
21
+ * @param {Quick_Capture_Quickidea1Inputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ const quick_capture_quickidea1 = /** @type {((inputs?: Quick_Capture_Quickidea1Inputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Quick_Capture_Quickidea1Inputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_quick_capture_quickidea1(inputs)
28
+ return zh_quick_capture_quickidea1(inputs)
29
+ });
30
+ export { quick_capture_quickidea1 as "quick_capture_quickIdea" }
@@ -0,0 +1,30 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Quick_Capture_Quickstory1Inputs */
7
+
8
+ const zh_quick_capture_quickstory1 = /** @type {(inputs: Quick_Capture_Quickstory1Inputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`快捷记录需求`)
10
+ };
11
+
12
+ const en_quick_capture_quickstory1 = /** @type {(inputs: Quick_Capture_Quickstory1Inputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Quick Story`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Quick Story" |
20
+ *
21
+ * @param {Quick_Capture_Quickstory1Inputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ const quick_capture_quickstory1 = /** @type {((inputs?: Quick_Capture_Quickstory1Inputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Quick_Capture_Quickstory1Inputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_quick_capture_quickstory1(inputs)
28
+ return zh_quick_capture_quickstory1(inputs)
29
+ });
30
+ export { quick_capture_quickstory1 as "quick_capture_quickStory" }
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Quick_Capture_QuitInputs */
7
+
8
+ const zh_quick_capture_quit = /** @type {(inputs: Quick_Capture_QuitInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`退出 AllBlue`)
10
+ };
11
+
12
+ const en_quick_capture_quit = /** @type {(inputs: Quick_Capture_QuitInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Quit AllBlue`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Quit AllBlue" |
20
+ *
21
+ * @param {Quick_Capture_QuitInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const quick_capture_quit = /** @type {((inputs?: Quick_Capture_QuitInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Quick_Capture_QuitInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_quick_capture_quit(inputs)
28
+ return zh_quick_capture_quit(inputs)
29
+ });