Skip to content

UI #3

Merged
wynxing merged 8 commits into
mainfrom
feat/status-board
Jun 15, 2026
Merged

UI #3
wynxing merged 8 commits into
mainfrom
feat/status-board

Conversation

@wynxing

@wynxing wynxing commented Jun 15, 2026

Copy link
Copy Markdown
Owner

add settting board,status board and test window feat

wynxing added 3 commits June 13, 2026 21:40
Independent settings webview window restored from settings.html prototype.
Phase 1 is presentational only — all controls are local refs; no backend
persistence wired up yet (DB settings table + REST routes belong to phase 2).

- new "settings" Tauri window (1080x760, decorated, hide-on-close like panel)
- new SettingsView with 6 sections: general / assistant / persona / model /
  privacy / hooks (1:1 content with prototype)
- useSettings composable aggregates state + actions
- styles/settings.css scoped to [data-view="settings"] so the prototypes
  light-glass + blue-accent visual does not pollute the global tokens
- bg image baked into .settings-root background-image (light/dark via class)
  to avoid the negative z-index trick that only works on <body>
- panel header gets a "设置" button to open the window via
  WebviewWindow.getByLabel("settings")
- App.vue skips refresh() / WS connect for the settings view

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

代码审查总结

此 PR 引入了 StatusBoard / SettingsPanel 的 UI 架构,整体设计方向清晰,组件拆分合理,v-if 逐页挂载的模式也避免了多余的 GPU 合成层。以下列出需要在合并前处理的问题,以及若干维护性建议。

需优先修复

  1. WeeklyFocusCard 柱状图数据合并错误bars 数组将 blueBarsgreyBars 拼接成了 14 项,导致图表显示 14 根柱子而非 7 组对比柱。正确做法是将每天的两个值配对成一个对象数组。
  2. CreateFocusCard 时长选择无效 — 卡片展示了 25 · 45 · 90 min 供用户选择,但点击整个卡片只会 emit startFocus(25)durations 数组从未被用于实际选择逻辑,应拆分为独立按钮或移除虚假的选择提示。
  3. quickStartFocus 移除了 activeTaskId 的空值守卫 — 旧版在 tasks.activeTaskId.value 为 null 时跳过调用;此 PR 直接将 null 传给 focus.startFocus,需确认 startFocus 能安全处理 null,否则需恢复守卫。
  4. FloatingControls 的事件未接入toggleTts / toggleImmersiveApp.vue 中无监听器,按钮对用户表现为可用,但实际不影响 useSettings 的任何状态。
  5. focusStartTime 硬编码为 "18:42"BottomBar 显示专注开始时间,但始终固定为 "18:42",需从 useFocusonStartFocus 记录真实时间。

安全问题

  1. AssistantStatusCard 使用 v-html 渲染 quote — 当前为硬编码字符串,但注释中明确计划 Phase 2 对接后端 WebSocket。tauri.conf.json 中 CSP 为 null,一旦 quote 来自动态数据(AI 回复、persona 文件、WS 消息),v-html 将带来 XSS 风险,攻击者可借此调用窗口 Tauri IPC 能力。建议将 <div v-html="quote"> 替换为静态模板(文字 + <em> 标签),在 Phase 2 到来前消除这一模式。
  2. settings 窗口继承了全量默认能力capabilities/default.json"settings" 加入与 main/panel 相同的能力集,包含 allow-internal-toggle-devtoolsopener:default(允许打开外部 URL)。建议为设置窗口创建单独的最小权限能力集,移除不需要的 opener 和 DevTools 权限。
  3. 自定义 API 端点无验证ModelSection 的自定义端点字段接受任意字符串;Phase 2 用此 URL 发送含 API Key 的请求时,需在 Rust 侧校验 URL scheme 为 https 且非 loopback/私有地址,防止 SSRF 将认证请求转发至本地 Hook API(同在 10103 端口)。

性能建议

  1. 多层 backdrop-filter 同时存在的 GPU 合成开销 — 顶部导航、各卡片、底部栏等超过 10 个元素同时持有 backdrop-filter,在 1400×900 的 Tauri WebView 中叠加的合成层会显著增加每帧 GPU 开销,尤其在 hover 动画期间。建议收敛到关键区域(如顶部导航容器),其余区域改用半透明纯色背景。
  2. SVG feTurbulence 噪点滤镜对每张卡片 ::after 单独计算filter: url(#panel-noise-filter) 无法复用缓存,每个元素都会触发一次湍流光栅化。视觉效果在 opacity: 0.045 下可忽略,建议改用一张静态 PNG 噪点纹理 + mix-blend-mode: overlay,在 CSS 层面无运行时开销。
  3. 全屏 position: fixed 伪元素 + mix-blend-mode: multiplypanel-shell::before/after 跨越整个视口并使用混合模式,阻止了浏览器的不透明层快速路径优化;任何子元素的动画都会导致整个合成栈重绘。建议在 .panel-shell 上添加 contain: paint 或将背景效果移至独立的非 fixed 层。

维护性建议

  • TopNav 接收 petState 但模板中从未引用该 prop,头像为完全静态的硬编码内联样式;建议在实现响应式头像前移除该 prop 声明,避免误导。
  • TopNav 定义了 search emit 但 App.vue 未监听,AiChatCard.vue 输入框内联了 6 条样式声明,WeeklyFocusCard 多处统计数据(12h 40min+1h 50min)及 DiaryCard 中的日期(06·12)均为硬编码,建议在静态占位处统一添加 // TODO: 接入真实数据 注释,与 ClawGatewayCardAssistantStatusCard 等已有注释的文件保持一致。
  • apiKeyMasked 默认值 "sk-••••••••3f7a" 会让未配置 Key 的用户误以为已有 Key,建议默认为空字符串并在 UI 侧条件渲染占位提示。
  • Phase 2 实现 API Key IPC 时,应确保明文 Key 仅在 Rust 侧完成掩码处理后再跨 IPC 传入渲染进程,避免明文 Key 驻留在 JS 堆中。

const blueBars = [35, 45, 30, 55, 40, 65, 50];
const greyBars = [45, 70, 60, 75, 55, 65, 50];
const bars = [
...blueBars.map((height) => ({ height, color: "#3b82f6" })),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

柱状图数据合并错误:当前将 blueBarsgreyBars 直接拼接为 14 项数组,模板迭代后会渲染 14 根柱子,而 weekDays 只有 7 个标签。正确方式是将两组数据按索引配对:

const bars = blueBars.map((blue, i) => ({ blue, grey: greyBars[i] }));

然后在模板中为每天渲染一对蓝/灰柱子。

<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M12 5v14M5 12h14" />
</svg>
<span style="font-weight: 500">开始新一段专注</span>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

durations 数组为死代码,时长选择无效:整张卡片是一个按钮,点击后固定 emit startFocus(25),用户无法选择 45 或 90 分钟。durations 仅用于展示字符串。建议拆分为三个独立按钮,或移除展示的时长列表并在注释中说明这是单次快速开始操作。

Comment thread apps/desktop/src/App.vue
@@ -184,10 +209,8 @@ function closeContextMenu() {

async function quickStartFocus() {
closeContextMenu();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

移除了 activeTaskId 的空值守卫:旧版 quickStartFocustasks.activeTaskId.value 为 null 时会跳过调用。现在直接将可能为 null 的值传入 focus.startFocus,请确认 startFocus 能安全处理 null 参数,否则需恢复守卫:

if (tasks.activeTaskId.value) {
  await focus.startFocus(tasks.activeTaskId.value);
}

Comment thread apps/desktop/src/App.vue
@send-chat="onSendChat"
@send-message="handleSendMessage"
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FloatingControls 事件未接入<FloatingControls /> 没有任何 @toggle-tts / @toggle-immersive 监听器,用户点击 TTS / 沉浸模式按钮时 emit 会丢失,不会影响 useSettings 中的对应状态。

Comment thread apps/desktop/src/App.vue
<AssistantStatusCard v-else-if="activePanelPage === 'assistant'" :pet-state="pet.petState.value" />

<HookNotificationsCard v-else-if="activePanelPage === 'hooks'" />

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

focusStartTime 硬编码focus-start-time="18:42" 是字面字符串,专注开始时间始终显示为 18:42。建议在 useFocus 中暴露会话开始时间戳,或在 onStartFocusFromCard / quickStartFocus 调用时记录 new Date() 并格式化后传入。

</div>

<div class="nc-mood">
<div class="orb"></div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

v-html + 无 CSP 存在 XSS 升级路径:当前 quote 是硬编码字符串,但 useSettings.ts 中的注释明确计划 Phase 2 接入后端 WS 消息。tauri.conf.json 的 CSP 为 null,如果届时 quote 来自 AI 回复、persona 文件或 WS 推送,v-html 将允许任意 HTML/脚本在渲染进程中执行,进而调用窗口的 Tauri IPC 能力。

建议在 Phase 2 前替换为静态模板:

<div class="quote">「你正在做<em>重要的事</em>,不要分心……」</div>

"identifier": "default",
"description": "Default desktop capability for NeoCompanion v1",
"windows": ["main", "panel", "wallpaper"],
"windows": ["main", "panel", "wallpaper", "settings"],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

设置窗口继承了过多默认能力"settings" 窗口与 main/panel 共享同一能力集,包含 allow-internal-toggle-devtools(允许从脚本打开 DevTools)和 opener:default(允许打开外部 URL / shell 操作)。建议为设置窗口单独创建一个最小权限能力文件,仅授予实际所需权限(如配置读写的路径访问),移除不必要的 opener 和 DevTools 权限。

}>();

const emit = defineEmits<{
toggleTheme: [];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

petState prop 未被模板使用petState: CompanionState 已在 defineProps 中声明,但模板中的头像完全由硬编码内联样式绘制,prop 值从未被读取。建议在实现响应式头像之前移除该 prop 声明,或添加注释说明头像样式将在后续迭代中依据 petState 动态变化。

@
fix: address PR #3 review feedback — bugs, security, and maintenance

- fix WeeklyFocusCard bar chart: pair blue/grey bars per weekday (14→7 groups)
- fix CreateFocusCard: make durations (25/45/90) interactively selectable
- fix quickStartFocus: restore null guard on activeTaskId
- fix onStartFocusFromCard: add null guard + record real focusStartTime
- fix FloatingControls: wire toggleTts/toggleImmersive events to useSettings
- fix AssistantStatusCard: remove v-html XSS vector, use static template
- fix apiKeyMasked: default to empty string instead of misleading placeholder
- chore TopNav: mark unused petState/search with Phase 2 TODO comments
- chore: add TODO markers on all hardcoded static data
- chore ModelSection: add TODO for Phase 2 URL validation (SSRF prevention)
- style AiChatCard: extract inline styles to .chat-input-native class
@
@wynxing

wynxing commented Jun 15, 2026

Copy link
Copy Markdown
Owner Author

感谢详细的 review!以下是逐条回复:\n\n### ✅ 已修复\n\n1. WeeklyFocusCard 柱状图数据合并错误 — 已将 bars 从 14 项扁平拼接改为按天配对,模板中每天渲染两根并排对比柱。\n2. CreateFocusCard 时长选择无效 — 已拆分为三个可互动的时长 chip (25/45/90),点击选择高亮,开始按钮 emit 选中的时长。\n3. quickStartFocus 空值守卫 — 已恢复 activeTaskId null 检查,null 时直接 return。onStartFocusFromCard 也已添加同款守卫。\n4. FloatingControls 事件接入 — 已在 App.vue 接入 toggleTts / toggleImmersive,连接 useSettings 状态。\n5. focusStartTime 硬编码 — 已改为在专注开始时记录真实时间,通过 ref 传入 BottomBar。\n6. v-html XSS 风险 — 已移除 v-html,改为静态模板。Phase 2 也不会再用 v-html 渲染动态内容。\n\n### ✅ 维护性修复\n\n- apiKeyMasked 默认值已改为空字符串\n- TopNav 未使用的 petState prop 和 search emit 已加 TODO: Phase 2 注释\n- 全部硬编码数据处已添加 TODO: 接入真实数据 注释\n- AiChatCard 内联样式已提取为 .chat-input-native CSS class\n- ModelSection 自定义端点字段旁已添加 Phase 2 SSRF 防范 TODO 注释\n\n### 📝 后续迭代处理\n\n7. Settings 窗口权限 — 当前 Phase 1 settings 为纯静态页面,Phase 2 接入真实配置时会创建独立最小权限 capability。\n8. API 端点验证 — Phase 2 会在 Rust 侧校验 URL scheme 为 https 并禁止 loopback/私有地址。\n9-11. 性能优化 — 噪音纹理改 PNG、收敛 backdrop-filter、contain:paint 等已记录 backlog。\nPhase 2 API Key 安全 — 同意,明文 Key 仅在 Rust 侧掩码后跨 IPC 传入渲染进程。

wynxing added 4 commits June 15, 2026 09:23
tauri-plugin-wallpaper depends on the windows crate which pulls in
windows-future. This crate fails to compile on macOS and Linux
because windows-future v0.2.1 references IMarshal/marshaler APIs
that are unavailable outside Windows targets.

Move the dependency to target cfg windows dependencies since it is
already gated with cfg target_os windows in lib.rs
The wallpaper:default permission in capabilities/default.json caused
tauri-build to fail on macOS/Linux because tauri-plugin-wallpaper is
now gated to Windows-only in Cargo.toml. Split into a separate
capability file with platforms: [windows] so non-Windows CI passes.
macos-latest runners are Apple Silicon and only have aarch64 target
installed. Building universal-apple-darwin requires both architectures.
Add rustup target add step for macOS in ci-build and release workflows.

@wynxing wynxing left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all front,can pass

@wynxing wynxing merged commit 8e4c5fd into main Jun 15, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant