@allbluecn/web-app 0.24.3 → 0.24.5

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.24.3",
3
+ "version": "0.24.5",
4
4
  "license": "AGPL-3.0-or-later",
5
5
  "type": "module",
6
6
  "files": [
@@ -65,18 +65,24 @@ afterEach(() => {
65
65
  })
66
66
 
67
67
  describe('DesktopSyncIndicator 三态徽标(A11)', () => {
68
- it('初始查询建立基线:guest 徽标 + i18n 文案', async () => {
68
+ // guest(本地模式)不再渲染徽标(2026-11-03 用户裁决)→ 无基线可等;
69
+ // 先 flush 初始 desktopState 查询(迟到 resolve 会覆盖后续推送态)
70
+ const flushInitialQuery = () =>
71
+ act(async () => {
72
+ await new Promise((r) => setTimeout(r, 0))
73
+ })
74
+
75
+ it('guest(本地模式)默认态不渲染徽标(2026-11-03 用户裁决移除常驻显示)', async () => {
69
76
  bridgeRef.current = makeBridge()
70
77
  render(<DesktopSyncIndicator />)
71
- const badge = await screen.findByTestId('desktop-state-badge')
72
- expect(badge.getAttribute('data-desktop-state')).toBe('guest')
73
- expect(badge.textContent).toContain(m.desktop_state_guest())
78
+ await flushInitialQuery()
79
+ expect(screen.queryByTestId('desktop-state-badge')).toBeNull()
74
80
  })
75
81
 
76
82
  it('事件推送 transferring:徽标随推送更新(过境态)', async () => {
77
83
  bridgeRef.current = makeBridge()
78
84
  render(<DesktopSyncIndicator />)
79
- await screen.findByTestId('desktop-state-badge')
85
+ await flushInitialQuery()
80
86
  await act(async () => {
81
87
  stateListener?.({ kind: 'transferring', updatedAt: '2026-11-01T00:00:01Z' })
82
88
  })
@@ -88,7 +94,7 @@ describe('DesktopSyncIndicator 三态徽标(A11)', () => {
88
94
  it('事件推送 cloud:徽标迁云态(身份切换终态)', async () => {
89
95
  bridgeRef.current = makeBridge()
90
96
  render(<DesktopSyncIndicator />)
91
- await screen.findByTestId('desktop-state-badge')
97
+ await flushInitialQuery()
92
98
  await act(async () => {
93
99
  stateListener?.({ kind: 'cloud', updatedAt: '2026-11-01T00:00:02Z' })
94
100
  })
@@ -116,15 +122,16 @@ describe('DesktopSyncIndicator 三态徽标(A11)', () => {
116
122
  expect(container.textContent).toBe('')
117
123
  })
118
124
 
119
- it('三态齐备时同步相位行与徽标共存(双行)', async () => {
125
+ it('三态齐备时同步相位行与徽标共存(双行,非 guest 态)', async () => {
120
126
  bridgeRef.current = makeBridge()
121
127
  render(<DesktopSyncIndicator />)
122
- await screen.findByTestId('desktop-state-badge')
128
+ await flushInitialQuery()
123
129
  await act(async () => {
130
+ stateListener?.({ kind: 'transferring', updatedAt: '2026-11-01T00:00:00Z' })
124
131
  syncListener?.({ phase: 'idle', pending: 0, lastError: null, lastSyncedAt: '2026-11-01' })
125
132
  })
126
133
  const wrapper = screen.getByTestId('desktop-sync-indicator')
127
- expect(wrapper.textContent).toContain(m.desktop_state_guest())
134
+ expect(wrapper.textContent).toContain(m.desktop_state_transferring())
128
135
  expect(wrapper.textContent).toContain('已同步')
129
136
  })
130
137
  })
@@ -85,7 +85,9 @@ export function DesktopSyncIndicator() {
85
85
 
86
86
  return (
87
87
  <div className="space-y-0.5" data-testid="desktop-sync-indicator">
88
- {deskState && (
88
+ {/* guest(本地模式)是默认态、常驻无信息量——用户裁决不展示(2026-11-03);
89
+ 仅展示过境/迁云等有信息量的状态 */}
90
+ {deskState && deskState !== "guest" && (
89
91
  <div
90
92
  className="flex items-center gap-1.5 px-2 py-1 text-xs text-muted-foreground"
91
93
  data-testid="desktop-state-badge"
@@ -560,21 +560,26 @@ export default function HarnessWorkbenchPage({ initialThreadId, initialMessages,
560
560
 
561
561
  const centerContent = (
562
562
  <div className="flex h-full flex-col">
563
- <div className="flex shrink-0 items-center gap-2 border-b px-4 py-3">
563
+ {/* 头部整行为桌面壳窗口拖动区 + 交互簇 no-drag(story-panel-header 同款,见
564
+ globals.css 注释);顶缘 h-6 拖动带只盖到 y=24,本行按钮 top≈21 需要
565
+ no-drag 减除才能完整可点 */}
566
+ <div className="app-region-drag flex shrink-0 items-center gap-2 border-b px-4 py-3">
564
567
  {engine === "local" ? (
565
568
  <span className="inline-flex items-center gap-1.5 rounded-md border border-border bg-muted/40 px-2 py-1 text-xs text-muted-foreground">
566
569
  <FolderOpen className="size-3.5" />
567
570
  {m.harness_engine_localBadge()}
568
571
  </span>
569
572
  ) : (
570
- <AgentPresetPicker
571
- value={selectedAgentId}
572
- onValueChange={handleAgentChange}
573
- agents={agents ?? []}
574
- />
573
+ <div className="app-region-no-drag min-w-0">
574
+ <AgentPresetPicker
575
+ value={selectedAgentId}
576
+ onValueChange={handleAgentChange}
577
+ agents={agents ?? []}
578
+ />
579
+ </div>
575
580
  )}
576
581
  <h2 className="truncate font-semibold">{activeConversation?.title ?? m.chat_newConversation()}</h2>
577
- <div className="ml-auto flex items-center gap-1">
582
+ <div className="app-region-no-drag ml-auto flex items-center gap-1">
578
583
  <Button
579
584
  size="icon"
580
585
  variant="ghost"
@@ -15,7 +15,12 @@
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
- import type { ReactNode } from 'react'
18
+ import type { ElementType, ReactNode } from 'react'
19
+ import { useNavigate } from '@tanstack/react-router'
20
+ import { ArrowLeft } from 'lucide-react'
21
+ import { Button } from '@/components/ui/button'
22
+ import { isDesktopEdition } from '@/lib/edition-desktop'
23
+ import { m } from '@/paraglide/messages.js'
19
24
  import { AnimatedThemeToggler } from '@/components/shared-kit/theme/animated-theme-toggler'
20
25
 
21
26
  interface AuthLayoutProps {
@@ -25,10 +30,27 @@ interface AuthLayoutProps {
25
30
  export function AuthLayout({ children }: AuthLayoutProps) {
26
31
  const left = findChild(children, AuthLayoutLeft)
27
32
  const right = findChild(children, AuthLayoutRight)
33
+ const navigate = useNavigate()
28
34
 
29
35
  return (
30
36
  <div className="flex min-h-screen">
31
37
  <div className="relative flex flex-1 items-center justify-center p-6">
38
+ {/* 桌面壳:认证页左上角返回(免登录态可随时回到应用)。浏览器/云端不展示——
39
+ redirect 流程中 history.back 会回到需登录页再被弹回登录,形成死循环 */}
40
+ {isDesktopEdition() && (
41
+ <Button
42
+ variant="ghost"
43
+ size="icon"
44
+ className="absolute left-6 top-6 size-8"
45
+ aria-label={m.auth_layout_back()}
46
+ onClick={() => {
47
+ if (window.history.length > 1) window.history.back()
48
+ else void navigate({ to: '/dashboard' })
49
+ }}
50
+ >
51
+ <ArrowLeft className="size-4" />
52
+ </Button>
53
+ )}
32
54
  <div className="absolute" style={{ top: '1.5rem', right: '1.5rem' }}>
33
55
  <AnimatedThemeToggler className="flex size-6 items-center justify-center rounded-full hover:bg-accent" />
34
56
  </div>
@@ -55,11 +77,11 @@ export function AuthLayoutRight({ children }: { children: ReactNode }) {
55
77
  return <>{children}</>
56
78
  }
57
79
 
58
- function findChild(children: ReactNode, type: any) {
80
+ function findChild(children: ReactNode, type: ElementType) {
59
81
  const arr = Array.isArray(children) ? children : [children]
60
82
  for (const child of arr) {
61
- if ((child as any)?.type === type) {
62
- return (child as any).props.children
83
+ if ((child as { type?: unknown })?.type === type) {
84
+ return (child as { props: { children: ReactNode } }).props.children
63
85
  }
64
86
  }
65
87
  return null
@@ -112,7 +112,7 @@ export const SDK_GROUPS: SdkGroup[] = [
112
112
  "items": [
113
113
  {
114
114
  "name": "@allbluecn/web-app",
115
- "version": "v0.24.3",
115
+ "version": "v0.24.5",
116
116
  "lastUpdate": "Nov 3, 2026",
117
117
  "source": "apps/web/package.json",
118
118
  "docsUrl": "https://github.com/allbluecn/allblue"
@@ -289,6 +289,7 @@ import { getLocale, experimentalStaticLocale } from "../runtime.js"
289
289
  /** @typedef {{}} Auth_Forgotpassword_Submitting1Inputs */
290
290
  /** @typedef {{}} Auth_Forgotpassword_Subtitle1Inputs */
291
291
  /** @typedef {{}} Auth_Forgotpassword_Title1Inputs */
292
+ /** @typedef {{}} Auth_Layout_BackInputs */
292
293
  /** @typedef {{}} Auth_Login_Createaccount1Inputs */
293
294
  /** @typedef {{}} Auth_Login_Defaultaction1Inputs */
294
295
  /** @typedef {{}} Auth_Login_Emaillabel1Inputs */
@@ -9953,6 +9954,20 @@ export { auth_forgotpassword_title1 as "auth_forgotPassword_title" }
9953
9954
  /**
9954
9955
  * | output |
9955
9956
  * | --- |
9957
+ * | "Back" |
9958
+ *
9959
+ * @param {Auth_Layout_BackInputs} inputs
9960
+ * @param {{ locale?: "zh" | "en" }} options
9961
+ * @returns {LocalizedString}
9962
+ */
9963
+ export const auth_layout_back = /** @type {((inputs?: Auth_Layout_BackInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Auth_Layout_BackInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
9964
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
9965
+ if (locale === "en") return __en.auth_layout_back(inputs)
9966
+ return __zh.auth_layout_back(inputs)
9967
+ });
9968
+ /**
9969
+ * | output |
9970
+ * | --- |
9956
9971
  * | "Create account" |
9957
9972
  *
9958
9973
  * @param {Auth_Login_Createaccount1Inputs} inputs
@@ -287,6 +287,7 @@
287
287
  /** @typedef {{}} Auth_Forgotpassword_Submitting1Inputs */
288
288
  /** @typedef {{}} Auth_Forgotpassword_Subtitle1Inputs */
289
289
  /** @typedef {{}} Auth_Forgotpassword_Title1Inputs */
290
+ /** @typedef {{}} Auth_Layout_BackInputs */
290
291
  /** @typedef {{}} Auth_Login_Createaccount1Inputs */
291
292
  /** @typedef {{}} Auth_Login_Defaultaction1Inputs */
292
293
  /** @typedef {{}} Auth_Login_Emaillabel1Inputs */
@@ -6873,6 +6874,10 @@ export const auth_forgotpassword_title1 = /** @type {(inputs: Auth_Forgotpasswor
6873
6874
  return /** @type {LocalizedString} */ (`Forgot Password`)
6874
6875
  };
6875
6876
 
6877
+ export const auth_layout_back = /** @type {(inputs: Auth_Layout_BackInputs) => LocalizedString} */ () => {
6878
+ return /** @type {LocalizedString} */ (`Back`)
6879
+ };
6880
+
6876
6881
  export const auth_login_createaccount1 = /** @type {(inputs: Auth_Login_Createaccount1Inputs) => LocalizedString} */ () => {
6877
6882
  return /** @type {LocalizedString} */ (`Create account`)
6878
6883
  };
@@ -287,6 +287,7 @@
287
287
  /** @typedef {{}} Auth_Forgotpassword_Submitting1Inputs */
288
288
  /** @typedef {{}} Auth_Forgotpassword_Subtitle1Inputs */
289
289
  /** @typedef {{}} Auth_Forgotpassword_Title1Inputs */
290
+ /** @typedef {{}} Auth_Layout_BackInputs */
290
291
  /** @typedef {{}} Auth_Login_Createaccount1Inputs */
291
292
  /** @typedef {{}} Auth_Login_Defaultaction1Inputs */
292
293
  /** @typedef {{}} Auth_Login_Emaillabel1Inputs */
@@ -6873,6 +6874,10 @@ export const auth_forgotpassword_title1 = /** @type {(inputs: Auth_Forgotpasswor
6873
6874
  return /** @type {LocalizedString} */ (`忘记密码`)
6874
6875
  };
6875
6876
 
6877
+ export const auth_layout_back = /** @type {(inputs: Auth_Layout_BackInputs) => LocalizedString} */ () => {
6878
+ return /** @type {LocalizedString} */ (`返回`)
6879
+ };
6880
+
6876
6881
  export const auth_login_createaccount1 = /** @type {(inputs: Auth_Login_Createaccount1Inputs) => LocalizedString} */ () => {
6877
6882
  return /** @type {LocalizedString} */ (`创建账号`)
6878
6883
  };
@@ -36,6 +36,7 @@ import { Button } from '@/components/ui/button'
36
36
  import type { Session } from '@/lib/auth/auth-client'
37
37
  import { getLocale } from '@/paraglide/runtime.js'
38
38
  import { m } from "@/paraglide/messages.js";
39
+ import { isDesktopEdition } from '@/lib/edition-desktop'
39
40
 
40
41
  export const Route = createRootRouteWithContext<RouterContext>()({
41
42
  beforeLoad: async () => {
@@ -83,7 +84,7 @@ function RootComponent() {
83
84
  }, [])
84
85
 
85
86
  return (
86
- <html lang={getLocale()} suppressHydrationWarning>
87
+ <html lang={getLocale()} suppressHydrationWarning className={isDesktopEdition() ? 'desktop-shell' : undefined}>
87
88
  <head>
88
89
  <HeadContent />
89
90
  </head>
@@ -27,15 +27,17 @@ import { usePresenceReport } from '@/lib/collab/use-presence-report'
27
27
  import { NavigationIndicator } from './-components/navigation-indicator'
28
28
  import { isDesktopEdition } from '@/lib/edition-desktop'
29
29
 
30
- // 桌面壳顶缘细拖动条:铺在 y 0-8 的结构性空带——布局根行 mt-2(8px) 之后才出现
31
- // 任何面板/侧栏内容,故 h-2 恒在按钮/文字上方、不遮挡交互。-webkit-app-region
32
- // 是几何区域语义(mousedown 落在矩形内即被吞、与 z-order 无关,见 globals.css
33
- // 注释),此前 h-11 盖住面板头部/页面工具栏吞掉全部顶部点击(2026-11-02 实踩)。
34
- // 全宽覆盖列表页顶部等无头部区域;面板头部空区的拖动由各头部整行 drag 提供。
35
- // 浏览器/云端部署 no-op。
30
+ // 桌面壳顶缘拖动带:从窗口顶铺到列表工具栏上缘(y 0-24)。布局结构保证:
31
+ // SidebarInset 自 mt-2(8px) 起、页面根 p-4(16px),最早的可交互控件出现在
32
+ // y=24(需求/BUG/测例列表工具栏),故 h-6 恒在按钮/文字上方、不遮挡交互;
33
+ // y<24 内的头部控件(侧栏红绿灯行按钮、PeekPanel/AI 面板头)均已在各自
34
+ // header 标 app-region-no-drag,会从本条区域中减去(几何区域语义,
35
+ // mousedown 落在 drag 矩形内即被吞、与 z-order 无关,见 globals.css 注释;
36
+ // 2026-11-02 h-11 全宽条曾吞掉全部顶部点击)。⚠ 新页面若把控件提到 y<24,
37
+ // 必须为它标 no-drag。浏览器/云端部署 no-op。
36
38
  function DesktopDragStrip() {
37
39
  if (!isDesktopEdition()) return null
38
- return <div aria-hidden className="app-region-drag fixed inset-x-0 top-0 z-0 h-2" />
40
+ return <div aria-hidden className="app-region-drag fixed inset-x-0 top-0 z-0 h-6" />
39
41
  }
40
42
 
41
43
  export const Route = createLazyFileRoute('/_nav')({
@@ -852,12 +852,24 @@ html.theme-transitioning *::after {
852
852
  ⚠ drag 是「几何区域」语义:mousedown 落在 drag 矩形内即被吞,与元素 z-index /
853
853
  绘制层级无关(z-0 拖动条压不住 z-10 面板,只有 no-drag 元素能从区域中减去)。
854
854
  仅两种 sanctioned 铺法(2026-11-02 实踩:顶缘 h-11 全宽条吞掉了 PeekPanel/
855
- AI 面板头部与页面工具栏的全部点击,已删):① _nav 顶缘 h-2 细条——铺在布局
856
- 根行 mt-2(8px) 之上的结构性空带,恒在按钮/文字上方;② 头部整行 drag + 交互
857
- 元素/簇逐个 no-drag(侧栏 header、面板头部)。禁止横跨交互内容的粗条。 */
855
+ AI 面板头部与页面工具栏的全部点击,已删):① _nav 顶缘 h-6 拖动带——从窗口
856
+ 顶到列表工具栏上缘(y=24,由 mt-2 + 页面 p-4 结构保证),页面控件不得高于
857
+ y<24,有例外必须标 no-drag;② 头部整行 drag + 交互元素/簇逐个 no-drag
858
+ (侧栏 header、面板头部)。禁止横跨交互内容的粗条。 */
858
859
  .app-region-drag {
859
860
  -webkit-app-region: drag;
860
861
  }
861
862
  .app-region-no-drag {
862
863
  -webkit-app-region: no-drag;
863
864
  }
865
+
866
+ /* 桌面壳原生毛玻璃侧栏(2026-11-03):Electron darwin 侧 vibrancy:'sidebar'
867
+ (Pro 仓 main.ts)在网页内容之下铺系统毛玻璃,desktop-shell 置透明 body 让其
868
+ 露出,侧栏卡片半透明染色 → 随壁纸/窗外内容隐约变化(macOS 原生质感)。
869
+ 内容卡片(bg-background)保持不透明;浏览器/云端无 desktop-shell 类,全 no-op。 */
870
+ html.desktop-shell body {
871
+ background-color: transparent;
872
+ }
873
+ html.desktop-shell [data-slot='sidebar-inner'] {
874
+ background-color: color-mix(in srgb, var(--sidebar) 72%, transparent);
875
+ }