Skip to content

feat(desktop): 追踪 dashboard 侧栏面板 + codex 风格浮层交互 + opencode/nga 路径回退#137

Open
wanglongan587 wants to merge 3 commits into
ora-space:demofrom
wanglongan587:demo-dashboard
Open

feat(desktop): 追踪 dashboard 侧栏面板 + codex 风格浮层交互 + opencode/nga 路径回退#137
wanglongan587 wants to merge 3 commits into
ora-space:demofrom
wanglongan587:demo-dashboard

Conversation

@wanglongan587

@wanglongan587 wanglongan587 commented Jul 25, 2026

Copy link
Copy Markdown

概述

本 PR 为 Ora 桌面端引入代理追踪可视化 dashboard 功能,让用户在对话过程中以侧栏面板的形式实时查看 opencode / Claude Code 的追踪时序图与 token / 工具分析。同时附带若干工程化改进(路径解析回退、代码格式化)。

配套的 Streamlit 可视化器仓库:https://github.com/wanglongan587/agent_trace_vis


变更内容

1. 追踪 dashboard 侧栏面板(684d313

前端(packages/app-shell

  • 新增 TraceDashboardPanel 组件:以右侧 Sheet 浮层形式嵌入主界面,支持拖拽调宽(420–1400px,宽度持久化到 ui-store)。
  • workspace-view.tsx 中加入 dashboard 触发按钮,选中会话后可一键打开侧栏。
  • App.tsx 注入 Tauri 调用解析器,前端通过 agent_type + Ora 会话 id 解析 iframe URL,agent_session_id 全程不暴露给前端
  • 新增 i18n 键值(中英双语)。
  • mock-client 补充 gitIdentity namespace 以适配 upstream 新接口。

后端(apps/desktop/src-tauri

  • config.rs:新增 dashboardHost / dashboardPort 配置项(默认 127.0.0.1:8601)。
  • dashboard.rs:实现 get_dashboard_url 命令——解析追踪文件路径 → 写入 Locator 文件 → 健康探测 dashboard 服务 → 返回 iframe URL。
  • SessionLocator + resolve_session_locator(仅后端使用,agent_session_id 绝不暴露给前端)。
  • 支持 Claude(~/.claude/projects/**/<id>.jsonl)与 opencode(~/.local/share/opencode/trace/<id>.ndjson)两种 trace 路径;Claude 路径使用 home_directory 而非 cwd

2. codex 风格浮层面板交互(9d9d6ff

  • 将侧栏从内联 dock 重构为 @ora/ui 的共享 Sheet 原语,获得原生的:
    • 滑入 + 淡入 / 淡出动画(duration-200 ease-in-out,与 SettingsDialog 等保持一致)。
    • 遮罩点击关闭、右上角原生关闭按钮(absolute top-3 right-3)。
    • 关闭即卸载 iframe(重开时重新解析 Locator,Streamlit 常驻所以重连很快)。
  • 左缘拖拽手柄保留在 Sheet 内部,面板仍可调整宽度;持久化的像素宽度覆盖 Sheet 默认的 sm:max-w-sm(384px)上限。

3. opencode 路径解析支持 nga 回退(1309ce7

  • 重写 resolve_opencode_path(Windows 分支):依次在 PATH 上搜索 opencodenga,命中第一个即返回。部分用户的 OpenCode 发布产物以 nga 命名(与 opencode 共享相同数据模型与 trace 格式),此前会导致启动失败。
  • 错误信息同步更新为 "neither opencode nor nga executable found on PATH"。
  • 附带 cargo fmt --all 全量格式化(dashboard.rs / error.rs)及 Cargo.lock 传递依赖同步。

设计决策

决策 说明
dashboard 服务器外部管理 用户自行运行 Streamlit,Ora 仅知 host:port,不负责启停
Locator 文件数据通道 Ora 将 <appDataDir>/dashboard/<oraSessionId>.json = {traceFilePath, agentType} 写入磁盘;dashboard 通过操作系统约定(非 URL 参数)计算路径
agent_session_id 保持后端私有 URL 仅携带 Ora 会话 id;Ora Rust 解析 agent session id → trace 文件路径

测试

  • tsc:干净通过。
  • eslint:干净。
  • vite build:通过。
  • 单元测试:app-shell trace-dashboard 4/4 通过(另有 2 个 workspace 测试在纯 upstream/demo 上也失败,属 pre-existing 问题,非本 PR 引入)。
  • 端到端验证(真实 Streamlit + 真实 Chrome + 真实 opencode trace):
    • 侧栏滑入/淡入动画、遮罩点击关闭、拖拽调宽均正常。
    • 时序图 mermaid 渲染:iframe 自适应高度,无重叠、无间隙、session_end 矩形完整渲染。
    • opencode + nga 路径回退在仅安装 nga 的机器上验证通过。

🤖 Generated with excellent code assistant

@wanglongan587
wanglongan587 force-pushed the demo-dashboard branch 2 times, most recently from 4b78a67 to 1309ce7 Compare July 25, 2026 06:32
@wanglongan587 wanglongan587 changed the title feat(desktop): 追踪可视化仪表盘 + codex 风格侧栏面板 + opencode/nga 路径回退 feat(desktop): 追踪 dashboard 侧栏面板 + codex 风格浮层交互 + opencode/nga 路径回退 Jul 25, 2026

@wanglongan587 wanglongan587 left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

总结:整体方案清晰、ADR 文档到位,locator 文件通道把 agent_session_id 留在后端是个很好的安全设计,纯函数单测也扎实。主要问题集中在「切换会话时的状态陈旧」和「Claude transcript 全量扫描的健壮性/性能」两处,建议优先处理。

值得学习的地方:

  • ADR-0002/0003 的设计:磁盘 locator + agent session id 不出后端,避免了把敏感 id 放进 URL。
  • write_locator 的原子写 + fsync、config.rs 对 legacy 配置的 serde 兼容测试,都很到位。
  • 纯函数拆分干净,单测覆盖好;vite.config 忽略 src-tauri/target 解决 Windows EBUSY 是个实在的修复。

主要问题(按优先级):

  1. [建议修改] useDashboardEndpoint 切换会话时短暂展示旧 endpoint、无 loading 态(URL 里带着旧 session_id)——见 use-dashboard-endpoint.ts 行内。
  2. [建议修改] walkdir_files 全量收集 + 任一不可读子目录即整树失败,重度用户每次开面板都全量扫描 ~/.claude/projects——见 dashboard.rs 行内。
  3. [建议修改] validate_dashboard_endpoint 名实不符(说 loopback 只查非空)+ IPv6 会让 URL 失效——见 config.rs 行内。
  4. [建议修改] setter 手工逐字段重建 DesktopConfig,新增字段易漏改——见 config.rs 行内。
  5. [仅供参考] trace/locator 错误被吞成通用文案无 tracing、probe 与命令编排缺单测、file_for 缺路径清洗——见对应行内。

未单独行内列出的小点:

  • i18n 有几个未使用的 key(dashboard.close / dashboard.tab.review / dashboard.tab.terminal / dashboard.loadingFrame),若是预留 tab 建议加注释说明。
  • PanelResizeHandle 里 {...props} 写在显式 onPointerDown/Move/Up 之后,会把内置事件处理器覆盖掉,建议把 spread 挪到前面。
  • iframe sandbox="allow-scripts allow-same-origin ...":当前 dashboard 跨域(127.0.0.1 vs tauri origin),是安全的;但若 #3 把 host 配成 tauri 同源就会变危险,所以 #3 的 loopback 校验也顺带堵住了这条。
  • locator 文件按 session 累积、无清理;如不影响可后续加过期清理。

建议先修 #1(用户可见的错误数据)和 #2(健壮性),#3/#4 顺手一起改,#5 可拆到下一迭代。

if (!canResolve) return EMPTY_STATE;
return {
endpoint: resolved.endpoint,
isLoading: resolved.endpoint === null && resolved.error === null,

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[建议修改] 切换会话时会短暂展示上一个会话的 iframe URL,且不经过 loading 态

第 75 行的 isLoading 只在「从未拿到过 endpoint」时为 true(resolved.endpoint === null)。但 resolved 是跨会话复用的 state,切换 sessionId 时并没有被重置——effect 的 cleanup 只是把旧 promise 标记为 superseded,并没有清空 resolved。

复现:选中会话 A 打开面板(A 解析完成)→ 关闭面板 → 在侧栏选中会话 B → 重新打开面板。此时 canResolve 由 false→true,effect 重新发起 resolve(B),但在 B 解析完成前,hook 返回的 endpoint 仍是 A 的(URL 里 session_id=A),isLoading 为 false,于是 iframe 会带着 A 的 URL 先加载,直到后端 resolve(B) 返回(locator 写入 + 500ms 探测)才切换。对慢机器/慢磁盘这不止是闪一下,是展示了错误会话的数据。

建议二选一:

  • 在 resolved 里带上它对应的 sessionId,派生 isLoading 时若 resolved.sessionId !== currentSessionId 也算 loading;
  • 或在 effect 开头(sessionId 变化时)setResolved({ endpoint: null, error: null }) 重置,让 loading 态自然出现。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复(commit 795f55a)。

采用了第一个建议:在 resolved 中带上 sessionId,切换会话时若 resolved.sessionId !== currentSessionId 则视为 stale,派生 isLoading=true + endpoint=null,面板自然展示 loading 态,不再短暂加载旧会话的 iframe URL。

这样无需在 effect 里 setState 重置(保持了原有的"不在 effect 里同步 setState"设计),stale 判断完全在派生阶段完成。

Comment thread apps/desktop/src-tauri/src/dashboard.rs Outdated
}

/// Recursively yields files under `root` without depending on an external crate.
fn walkdir_files(root: &Path) -> std::io::Result<Box<dyn Iterator<Item = PathBuf>>> {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[建议修改] walkdir_files 签名承诺懒迭代,实现却是全量收集;且任一不可读子目录会让整棵扫描失败

两个问题:

  1. 返回类型是 Box<dyn Iterator<Item = PathBuf>>,暗示调用方可以「边遍历边早返回」,但实现先把整棵 ~/.claude/projects 收集进 Vec 再返回迭代器。于是 resolve_claude_code_trace 里的 return Ok(Some(path)) 早返回只省掉了字符串比较,省不掉 I/O。对重度 Claude Code 用户(projects 下可能几千个 .jsonl),每次打开面板都要全量扫一次。

  2. std::fs::read_dir(&dir)? 把子目录不可读(权限/被占用)直接传播成 Err,导致整次解析失败、面板报「无法解析 trace」。单个无权限子目录不该让整个面板不可用。

建议:

  • 要么把签名改成 Vec(诚实表达 eager),同时改成「遇到不可读子目录就 continue 并 tracing::warn! 一条」;
  • 要么真正流式化(递归 generator 或直接引入 walkdir crate),让早返回能省 I/O。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复(commit 795f55a)。

改成了真正惰性的 WalkDirIter(自定义 Iterator 实现),签名仍为 Box<dyn Iterator<Item=PathBuf>> 但现在每次只 read_dir 一层、遇到文件即 yield,调用方的 return Ok(Some(path)) 早返回能真正省 I/O。

不可读子目录改为 tracing::warn!(?dir, ?e, "skipping unreadable directory during trace scan")continue,不再让单个权限问题拖垮整棵扫描。

Comment thread apps/desktop/src-tauri/src/config.rs Outdated

/// Rejects ambiguous dashboard endpoints before they become active runtime configuration.
pub(crate) fn validate_dashboard_endpoint(host: &str, port: u16) -> Result<(), DesktopConfigError> {
if host.trim().is_empty() {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[建议修改] 校验说 loopback address 却只查非空,名实不符;IPv6 loopback 还会让 URL 构造失效

第 235 行只判断了 host.trim().is_empty() 和 port == 0,但错误信息和文档(dashboard_host 注释、ADR-0001)都说是 loopback。目前用户可以把 dashboard_host 配成 0.0.0.0、192.168.x.x、甚至 tauri.localhost——后者会让 iframe 与宿主同源,配合下文 allow-scripts allow-same-origin 会变成不安全的组合。

另外若用户配 ::1(这也是 loopback),dashboard_url / probe_dashboard_server 里的 format!("{host}:{port}") 会生成 ::1:8601 这种非法地址(IPv6 需要 [::1]:8601)。

建议二选一:

  • 真正校验 loopback:host.parse::() 后判断 is_loopback(),或限定 127.0.0.1/localhost/::1 白名单;同时在 dashboard_url / probe_dashboard_server 里对 IPv6 加方括号;
  • 或把文案改成 non-empty host,与实现一致。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复(commit 795f55a),采用了第一个建议——真正校验 loopback。

  • is_loopback_hosthost.parse::<IpAddr>().is_loopback()localhost(不区分大小写),其余一律拒绝。
  • 新增 DesktopConfigError::DashboardHostNotLoopbackerror.rs 同步映射为 dashboard_host_not_loopback
  • 新增 format_host_port 辅助函数,IPv6 加方括号([::1]:8601),dashboard_urlprobe_dashboard_server 均已改用之。
  • 补充了测试:192.168.1.5DashboardHostNotLoopbackformat_host_port("::1", 8601)"[::1]:8601"

Comment thread apps/desktop/src-tauri/src/config.rs Outdated
let updated = DesktopConfig {
version: CONFIG_VERSION,
worktree_root,
dashboard_host: config.dashboard_host.clone(),

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[建议修改] setter 里手工逐字段重建 DesktopConfig,新增字段时极易漏改

set_worktree_root(118 行)和 set_dashboard_endpoint 都是用「把所有字段列一遍」的方式构造 updated。这个 PR 正是因为新增了两个 dashboard 字段,才不得不回来给旧的 set_worktree_root 补 dashboard_host.clone() / dashboard_port——下一次再加字段(比如某个 proxy 配置)时,很容易忘掉某一处 setter,导致那个字段被静默重置回默认值,且不会有编译错误。

建议改成结构更新语法,只写要改的字段:

let updated = DesktopConfig {
    version: CONFIG_VERSION,
    worktree_root,
    ..config.clone()
};

set_dashboard_endpoint 同理。这样未来加字段只需保证 DesktopConfig: Clone(已 derive),各 setter 自动携带新字段。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复(commit 795f55a)。

set_worktree_rootset_dashboard_endpoint 都改成了结构更新语法:

let updated = DesktopConfig {
    version: CONFIG_VERSION,
    worktree_root,
    ..config.clone()
};

未来新增字段只需保证 DesktopConfig: Clone(已 derive),各 setter 自动携带,不再有漏改风险。

Comment thread apps/desktop/src-tauri/src/dashboard.rs Outdated
) {
Ok(Some(path)) => Some(path.to_string_lossy().into_owned()),
Ok(None) => None,
Err(_) => {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[仅供参考] trace 解析与 locator 写入的错误被吞成通用文案,没有 tracing 落盘,排障困难

229 行的 Err(_) => 把 io::Error(权限不足?符号链接环?编码错误?)直接映射成 "failed to scan the agent trace directory";252 行 locator 写入同理。用户看到的是同一个通用文案,无法区分「Claude projects 目录权限不足」和「trace 文件还没生成」。crate 里已经有 tracing,建议在映射前 tracing::warn!(?err, ...) 落一条,至少日志里能区分根因。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复(commit 795f55a)。

trace 解析和 locator 写入的 Err(_) 分支都改成了先 tracing::warn!(?err, ...) 落盘根因,再映射为通用文案。日志里现在能区分"权限不足""符号链接环""trace 未生成"等不同场景。

}

/// Returns true when something answers a TCP connect on host:port within a beat.
async fn probe_dashboard_server(host: &str, port: u16) -> bool {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[仅供参考] probe_dashboard_server 与 get_dashboard_url 命令本身没有单测

纯函数(dashboard_url / write_locator / resolve_*_trace)测得很扎实,但 500ms 超时 / 连接拒绝 → server_reachable=false 这条分支,以及命令「写 locator → 探测 → 返回」的编排没有被覆盖。probe_dashboard_server 比较好测:对一个空闲端口 connect 应返回 false,对一个本地 listener 应返回 true。建议补上,避免以后改超时阈值或探测策略时静默回归。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复(commit 795f55a)。

补充了两个 #[tokio::test]

  • probe_returns_false_for_closed_port:bind 后立即 drop 得到空闲端口,探测应返回 false。
  • probe_returns_true_for_open_port:本地 listener + 后台 accept,探测应返回 true。

命令编排(get_dashboard_url 的写 locator → 探测 → 返回)依赖完整 DesktopState,mock 成本较高,暂留到后续集成测试迭代。

}

/// Returns the locator file path for one Ora session id.
pub fn file_for(app_data_directory: &Path, ora_session_id: &str) -> PathBuf {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

[仅供参考] file_for 没有对 ora_session_id 做路径清洗,靠 find_session 间接防穿越

第 61 行 format!("{ora_session_id}.json") 若 ora_session_id 含 .. 或路径分隔符,会写到 dashboard/ 之外。目前安全是因为 get_dashboard_url 在写 locator 前会先调 resolve_session_locator → find_session(mod.rs 465 行)做 DB 精确查询,不存在的 session 直接 NotFound,所以穿越串进不来。但这层防护是隐式的、依赖调用顺序。

建议在 file_for 里显式拒绝含 ..、/ 或反斜杠的 id(或断言为合法 UUID 形态),把约束放在真正使用它的地方,防止未来有人直接调 write_locator 绕过 DB 校验。

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

已修复(commit 795f55a)。

file_for 中新增 debug_assert_path_safe(id),显式拒绝含 /\.. 的 session id。约束放在真正使用它的地方,防止未来有人直接调 write_locator 绕过 DB 校验。

wanglongan587 added a commit to wanglongan587/desktop that referenced this pull request Jul 25, 2026
根据 PR ora-space#137 检视意见修复:

1. useDashboardEndpoint 切换会话时不再展示旧 endpoint:
   在 resolved 中追踪 sessionId,切换时旧数据视为 stale,
   派生 isLoading=true + endpoint=null,面板展示 loading 态。

2. walkdir_files 改为真正惰性迭代器(WalkDirIter),
   早返回可省 I/O;不可读子目录 tracing::warn! 后跳过,
   不再让单个权限问题拖垮整棵扫描。

3. validate_dashboard_endpoint 真正校验 loopback
   (IpAddr::is_loopback() 或 localhost),拒绝非回环地址,
   堵住 tauri.localhost 同源 + allow-scripts 的沙箱风险;
   新增 DashboardHostNotLoopback 错误变体。

4. config setters 改用结构更新语法(..config.clone()),
   未来新增字段自动携带,不再逐字段手工重建。

5. dashboard.rs 错误映射前加 tracing::warn! 落盘根因,
   区分权限不足 / trace 未生成等不同场景。

6. probe_dashboard_server 补充单测(空闲端口→false,
   本地 listener→true);format_host_port 对 IPv6 加方括号,
   修复 ::1:8601 非法地址。

7. file_for 加 debug_assert_path_safe 路径清洗,
   显式拒绝含 / \ .. 的 session id(纵深防御)。

8. PanelResizeHandle 的 {...props} 移到显式事件处理器之前,
   防止外部 onPointerDown 覆盖内部拖拽逻辑。

9. 清理未使用的 i18n key(dashboard.close / tab.review /
   tab.terminal / loadingFrame)。
wanglongan587 added a commit to wanglongan587/desktop that referenced this pull request Jul 25, 2026
根据 PR ora-space#137 检视意见修复:

1. useDashboardEndpoint 切换会话时不再展示旧 endpoint:
   在 resolved 中追踪 sessionId,切换时旧数据视为 stale,
   派生 isLoading=true + endpoint=null,面板展示 loading 态。

2. walkdir_files 改为真正惰性迭代器(WalkDirIter),
   早返回可省 I/O;不可读子目录 tracing::warn! 后跳过,
   不再让单个权限问题拖垮整棵扫描。

3. validate_dashboard_endpoint 真正校验 loopback
   (IpAddr::is_loopback() 或 localhost),拒绝非回环地址,
   堵住 tauri.localhost 同源 + allow-scripts 的沙箱风险;
   新增 DashboardHostNotLoopback 错误变体。

4. config setters 改用结构更新语法(..config.clone()),
   未来新增字段自动携带,不再逐字段手工重建。

5. dashboard.rs 错误映射前加 tracing::warn! 落盘根因,
   区分权限不足 / trace 未生成等不同场景。

6. probe_dashboard_server 补充单测(空闲端口→false,
   本地 listener→true);format_host_port 对 IPv6 加方括号,
   修复 ::1:8601 非法地址。

7. file_for 加 debug_assert_path_safe 路径清洗,
   显式拒绝含 / \ .. 的 session id(纵深防御)。

8. PanelResizeHandle 的 {...props} 移到显式事件处理器之前,
   防止外部 onPointerDown 覆盖内部拖拽逻辑。

9. 清理未使用的 i18n key(dashboard.close / tab.review /
   tab.terminal / loadingFrame)。
@wanglongan587

Copy link
Copy Markdown
Author

感谢详细的检视意见,已全部处理(commit 795f55a)。

行内 7 条已逐条回复,总结中的小点也一并修了:

  • i18n 未使用 key:删除了 dashboard.closedashboard.tab.reviewdashboard.tab.terminaldashboard.loadingFrame(review/terminal tab 已移除,不再预留)。
  • PanelResizeHandle spread 顺序{...props} 移到了显式 onPointerDown/Move/Up 之前,外部传入的同类 handler 不再覆盖内部拖拽逻辑。
  • locator 累积无清理:暂留后续迭代,当前不影响功能。

测试全绿:Rust 22/22(新增 4 个:probe true/false、IPv6 URL、format_host_port),前端 tsc/eslint/vitest 4/4 全过。

wanglongan587 added a commit to wanglongan587/desktop that referenced this pull request Jul 25, 2026
根据 PR ora-space#137 检视意见修复:

1. useDashboardEndpoint 切换会话时不再展示旧 endpoint:
   在 resolved 中追踪 sessionId,切换时旧数据视为 stale,
   派生 isLoading=true + endpoint=null,面板展示 loading 态。

2. walkdir_files 改为真正惰性迭代器(WalkDirIter),
   早返回可省 I/O;不可读子目录 tracing::warn! 后跳过,
   不再让单个权限问题拖垮整棵扫描。

3. validate_dashboard_endpoint 真正校验 loopback
   (IpAddr::is_loopback() 或 localhost),拒绝非回环地址,
   堵住 tauri.localhost 同源 + allow-scripts 的沙箱风险;
   新增 DashboardHostNotLoopback 错误变体。

4. config setters 改用结构更新语法(..config.clone()),
   未来新增字段自动携带,不再逐字段手工重建。

5. dashboard.rs 错误映射前加 tracing::warn! 落盘根因,
   区分权限不足 / trace 未生成等不同场景。

6. probe_dashboard_server 补充单测(空闲端口→false,
   本地 listener→true);format_host_port 对 IPv6 加方括号,
   修复 ::1:8601 非法地址。

7. file_for 加 debug_assert_path_safe 路径清洗,
   显式拒绝含 / \ .. 的 session id(纵深防御)。

8. PanelResizeHandle 的 {...props} 移到显式事件处理器之前,
   防止外部 onPointerDown 覆盖内部拖拽逻辑。

9. 清理未使用的 i18n key(dashboard.close / tab.review /
   tab.terminal / loadingFrame)。
为 Ora 桌面端集成代理追踪可视化 dashboard,以右侧侧栏面板形式
展示 opencode / Claude Code 的追踪时序图与 token / 工具分析。

后端(apps/desktop/src-tauri):
- config.rs:新增 dashboardHost(127.0.0.1)/ dashboardPort(8601)
  配置项,附 serde 默认值,保证旧版 config.json 仍可加载
- SessionLocator + resolve_session_locator(仅后端使用,
  agent_session_id 绝不暴露给前端)
- dashboard.rs:get_dashboard_url 命令——按 agent 类型解析追踪文件、
  写入 locator、探测 host:port 健康度、返回 iframe URL
- 支持 Claude(~/.claude/projects/**/<id>.jsonl)与
  opencode(~/.local/share/opencode/trace/<id>.ndjson)两种 trace 路径
- resolve_claude_code_trace 使用 home_directory 而非 cwd

前端(packages/app-shell):
- ui-store:dashboardOpen / dashboardWidth 状态
- 可拖拽调宽的侧栏面板(pointer-capture 拖拽手柄)
- TraceDashboardPanel 直接渲染 dashboard 内容
- App.tsx 注入 Tauri invoke 解析器
- 新增 i18n 键值(中英双语)
- mock-client 补充 gitIdentity namespace 以适配 upstream 新接口

vite.config.ts:忽略 src-tauri/target 目录,避免 EBUSY 监听崩溃。
将侧栏从内联 dock 重构为 @ora/ui 的共享 Sheet 原语,
获得 codex/ChatGPT 风格的交互体验:

- 打开时滑入 + 淡入、关闭时淡出(duration-200 ease-in-out,
  与 SettingsDialog 等保持统一的对话框节奏)
- 点击遮罩关闭
- 原生关闭按钮(top-3 right-3)
- 关闭即卸载 iframe(重开时重新解析 locator;Streamlit 常驻,
  重连成本极低)

左缘拖拽手柄保留在 Sheet 内部,面板仍可调整宽度;
持久化的像素宽度覆盖 Sheet 默认的比例宽度(inline width +
maxWidth:none 覆盖 data-[side=right]:sm:max-w-sm 的 384px 上限)。
根据 PR ora-space#137 检视意见修复:

1. useDashboardEndpoint 切换会话时不再展示旧 endpoint:
   在 resolved 中追踪 sessionId,切换时旧数据视为 stale,
   派生 isLoading=true + endpoint=null,面板展示 loading 态。

2. walkdir_files 改为真正惰性迭代器(WalkDirIter),
   早返回可省 I/O;不可读子目录 tracing::warn! 后跳过,
   不再让单个权限问题拖垮整棵扫描。

3. validate_dashboard_endpoint 真正校验 loopback
   (IpAddr::is_loopback() 或 localhost),拒绝非回环地址,
   堵住 tauri.localhost 同源 + allow-scripts 的沙箱风险;
   新增 DashboardHostNotLoopback 错误变体。

4. config setters 改用结构更新语法(..config.clone()),
   未来新增字段自动携带,不再逐字段手工重建。

5. dashboard.rs 错误映射前加 tracing::warn! 落盘根因,
   区分权限不足 / trace 未生成等不同场景。

6. probe_dashboard_server 补充单测(空闲端口→false,
   本地 listener→true);format_host_port 对 IPv6 加方括号,
   修复 ::1:8601 非法地址。

7. file_for 加 debug_assert_path_safe 路径清洗,
   显式拒绝含 / \ .. 的 session id(纵深防御)。

8. PanelResizeHandle 的 {...props} 移到显式事件处理器之前,
   防止外部 onPointerDown 覆盖内部拖拽逻辑。

9. 清理未使用的 i18n key(dashboard.close / tab.review /
   tab.terminal / loadingFrame)。
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