@allbluecn/web-app 0.26.0 → 0.26.1

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.26.0",
3
+ "version": "0.26.1",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "files": [
@@ -7,11 +7,20 @@
7
7
  // 自判返回 null)。
8
8
  // 云登录入口在侧边栏用户菜单(isLocalGuest → /login)与 /settings/sync
9
9
  // 设置页卡片,不在壳层悬浮表单(2026-10-31 用户裁决移除右下角表单)。
10
+ import { useLocation } from "@tanstack/react-router"
11
+
10
12
  import { DesktopSyncIndicator } from "@/components/desktop/sync-indicator"
11
13
  import { DesktopConflictCenter } from "@/components/desktop/conflict-center"
12
14
  import { DesktopStateListener } from "@/components/desktop/desktop-state-listener"
13
15
 
14
16
  export function DesktopShellWidgets() {
17
+ const pathname = useLocation().pathname
18
+ // mini 快捷记录面板(/quick-capture 独立 BrowserWindow)同属
19
+ // DEPLOY_TARGET=desktop,Providers 全页挂载本组件 → 右下角悬浮容器
20
+ // (fixed right-4 bottom-4 z-50 w-80)会叠在 360×480 面板之上压住底部
21
+ // 提示行,面板页提前不渲染(高亮判断同款纪律:用 useLocation,禁
22
+ // useRouter().state.location.pathname——后者是 stale 值不随导航更新)
23
+ if (pathname === "/quick-capture") return null
15
24
  return (
16
25
  <>
17
26
  {/* 监听器无 UI(订阅身份切换 → 缓存三件套失效),置于悬浮容器外防其
@@ -15,7 +15,7 @@
15
15
  // You should have received a copy of the GNU Affero General Public License
16
16
  // along with this program. If not, see <https://www.gnu.org/licenses/>.
17
17
 
18
- // 桌面托盘 mini 快捷记录面板(QuickCapturePanel)8 用例:
18
+ // 桌面托盘 mini 快捷记录面板(QuickCapturePanel)9 用例:
19
19
  // 1) 双 tab 渲染 + 默认 story 占位符
20
20
  // 2) 灵感 tab 下 Enter 保存:quickCaptureStoryFn 收到 inspiration 实参
21
21
  // 3) 保存成功:显示已记录 identifier、输入框清空、notifyQuickCapture 通知桌面壳
@@ -24,6 +24,7 @@
24
24
  // 6) Esc 隐藏窗口(bridge.window 收到 "hide"),不触发保存
25
25
  // 7) onMiniPanelMode 下发 inspiration 切换 mode;unmount 触发退订
26
26
  // 8) 无桥(浏览器直开):渲染与保存不抛错(可选链降级)
27
+ // 9) 请求在途期间再 Enter 不重复提交(防重,deferred mock 手动 resolve)
27
28
  // 桥与 serverFn 全 mock(jsdom 无 Electron 壳;getDesktopBridge mock 同款
28
29
  // desktop-state-listener 测试)。
29
30
  import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -145,4 +146,27 @@ describe('QuickCapturePanel', () => {
145
146
  await user.keyboard('{Escape}')
146
147
  expect(bridgeStub.window).not.toHaveBeenCalled()
147
148
  })
149
+
150
+ it('请求在途期间再 Enter 不重复提交(防重,deferred mock)', async () => {
151
+ // 首次调用挂起(手动 resolve),往返期间第二次 Enter 应被忽略
152
+ let resolveFn: (v: { identifier: string; name: string }) => void = () => {}
153
+ quickCaptureStoryFnMock.mockImplementationOnce(
154
+ () =>
155
+ new Promise((resolve) => {
156
+ resolveFn = resolve
157
+ }),
158
+ )
159
+ const user = userEvent.setup()
160
+ render(<QuickCapturePanel initialMode="story" />)
161
+ const input = screen.getByPlaceholderText('记一个需求…') as HTMLTextAreaElement
162
+ await user.type(input, '想法F')
163
+ await user.keyboard('{Enter}')
164
+ expect(quickCaptureStoryFnMock).toHaveBeenCalledTimes(1)
165
+ await user.keyboard('{Enter}')
166
+ expect(quickCaptureStoryFnMock).toHaveBeenCalledTimes(1)
167
+ expect(input).toHaveValue('想法F')
168
+ resolveFn({ identifier: 'STORY-9', name: '想法F' })
169
+ expect(await screen.findByText('已记录 STORY-9')).toBeTruthy()
170
+ await waitFor(() => expect(input).toHaveValue(''))
171
+ })
148
172
  })
@@ -41,6 +41,8 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
41
41
  const [value, setValue] = useState("")
42
42
  const [savedIdentifier, setSavedIdentifier] = useState<string | null>(null)
43
43
  const feedbackTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)
44
+ // 保存防重:请求在途(value 尚未清空)期间忽略再次 Enter,防重复落库
45
+ const savingRef = useRef(false)
44
46
 
45
47
  // main 侧下发面板当前 tab(托盘菜单「记需求/记灵感」点击后推送);
46
48
  // unsubscribe 进 cleanup,防止窗口复用时叠加订阅
@@ -58,8 +60,10 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
58
60
  }, [])
59
61
 
60
62
  const save = async () => {
63
+ if (savingRef.current) return
61
64
  const name = value.trim()
62
65
  if (!name) return
66
+ savingRef.current = true
63
67
  try {
64
68
  const res = await quickCaptureStoryFn({ data: { kind: mode, name } })
65
69
  setSavedIdentifier(res.identifier)
@@ -70,6 +74,8 @@ export function QuickCapturePanel({ initialMode }: { initialMode: QuickCaptureMo
70
74
  feedbackTimerRef.current = setTimeout(() => setSavedIdentifier(null), FEEDBACK_CLEAR_MS)
71
75
  } catch {
72
76
  toast.error(m.quick_capture_failed())
77
+ } finally {
78
+ savingRef.current = false
73
79
  }
74
80
  }
75
81
 
@@ -112,7 +112,7 @@ export const SDK_GROUPS: SdkGroup[] = [
112
112
  "items": [
113
113
  {
114
114
  "name": "@allbluecn/web-app",
115
- "version": "v0.26.0",
115
+ "version": "v0.26.1",
116
116
  "lastUpdate": "Nov 5, 2026",
117
117
  "source": "apps/web/package.json",
118
118
  "docsUrl": "https://github.com/allbluecn/allblue"