Skip to content

feat: simplify focus session URL handling#421

Merged
J3n5en merged 2 commits into
J3n5en:mainfrom
zwmmm:feat/focus-session
Apr 11, 2026
Merged

feat: simplify focus session URL handling#421
J3n5en merged 2 commits into
J3n5en:mainfrom
zwmmm:feat/focus-session

Conversation

@zwmmm

@zwmmm zwmmm commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Remove cwd parameter from focus URL parsing, now only sessionId is required.
When switching sessions, use the session's own cwd instead of accepting an override.

zwmmm and others added 2 commits April 10, 2026 10:30
Adds URL scheme endpoint to receive session focus requests:
- URL format: enso://focus?session=<sessionId>&cwd=<path>
- Parses focus URLs in main process via parseFocusUrl()
- Sends APP_FOCUS_SESSION IPC to renderer
- Renderer uses useFocusSession hook to:
  - Focus session by sessionId via agentSessionsStore
  - Switch to chat tab
  - Fallback to worktree switch if only cwd provided
  - Silently ignore if session not found

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Remove cwd parameter from focus URL parsing, now only sessionId is required.
When switching sessions, use the session's own cwd instead of accepting an override.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Apr 10, 2026

Copy link
Copy Markdown
Contributor

Claude finished @zwmmm's task in 4m 7s —— View job


🔍 代码审查报告

👋 欢迎查看审查结果! 本次审查已完成,以下是详细分析和建议。
💡 使用提示:点击 📁 文件链接可直接跳转到代码位置,建议按优先级处理问题。

📋 审查进度

  • 获取 PR 上下文和变更文件列表
  • 读取变更文件内容 (7 个文件)
  • 分析代码质量、安全性、性能
  • 检查项目规范符合性
  • 生成详细审查报告

📊 审查概览 · 一目了然

📋 审查项目 🔢 发现数量 📈 占比 🎯 处理状态
📁 审查文件 7 100% 已完成
🚨 严重问题 0 0% 无需处理
⚠️ 中等问题 2 33% 🟡 本版本修复
轻微问题 4 67% 🔵 后续优化

🏆 代码质量评分

📊 综合评分: 86/100  🌟🌟🌟🌟⭐
📋 本次等级: 良好

📋 问题分布

🚨 严重: ░░░░░░░░░░ 0%
⚠️ 中等: ██████░░░░ 33%
⚡ 轻微: ██████░░░░ 67%
总体评分: ⭐⭐⭐⭐☆ (4.3/5)

🎯 关键发现

🏷️ 类别 🔍 发现数量 🎯 优先级 📈 说明
🔒 安全性 0 个 ✅ 通过 会话 ID 先查库再使用,无注入风险
⚡ 性能 0 个 ✅ 通过 轻量级监听器,无性能隐患
📚 文档一致性 1 个 🟡 中 设计文档与实现不符
🎨 代码质量 3 个 🔵 低 时序、窗口恢复、类型声明顺序
🧪 可测试性 1 个 🔵 低 新增 URL 解析函数缺乏单元测试
🛠️ 健壮性 1 个 🟡 中 未找到会话时静默失败,缺乏反馈

✨ 本次 PR 亮点

  • 遵循现有模式:新增 IPC 通道、preload 封装、useFocusSession Hook 的结构与 APP_OPEN_PATHuseOpenPathListener 保持一致。
  • 考虑了冷启动场景:引入 pendingFocusSessiondid-finish-load 配合,避免窗口未就绪时事件丢失。
  • 兼容两种 URL 解析形式parsed.host === 'focus'parsed.pathname === '//focus' 并存,跨平台兼容性更好。
  • 安全性良好sessionId 是先在 store 中查找再使用,不会因外部 URL 注入任意值。
  • 简化后的 API 设计更合理:移除 cwd 参数后,会话 ID 足以定位,session.cwd 作为权威来源避免了 URL 中路径被篡改的风险。

🐛 问题清单与修复指南 · 📋 点击展开查看

📋 👆 点击此处展开详细问题列表 (共 6 个问题)

🚀 快速开始

  • 📍 点击文件路径可直接跳转到问题代码
  • ⏰ 建议按 P1 → P2 优先级顺序处理
  • 📝 每个问题都包含具体修复方案

⚠️ 中等级别 (Major) 建议修复

🟡 #1 docs/superpowers/specs/2026-04-10-url-scheme-focus-session-design.md:5-72

🏷️ 类型: 文档一致性 | ⏰ 优先级: P1 - 本版本修复 | 🎯 影响范围: 文档

  • 🔍 问题描述:设计文档仍描述 cwd 参数及基于 cwd 的回退逻辑,但本次 PR(fix: simplify focus session URL handling)已经移除了对 cwd 的处理。PR 标题和文档内容出现明显冲突。
Extend the existing `enso://` protocol to support focusing specific sessions via URL. When a user opens `enso://focus?session=<sessionId>&cwd=<path>`, the app switches to the corresponding tab/pane.

## URL Format

enso://focus?session=&cwd=

| Parameters | Behavior |
|------------|----------|
| `session` only | Find and focus session by ID, switch to chat tab |
| `session` + `cwd` | Focus session within specified worktree |
| `cwd` only | Switch to specified worktree |
| Neither found | Silently ignore |
  • 🛠️ 解决方案

    1. 删除 URL 示例中的 &cwd=<path> 部分
    2. Parameters 表只保留 session only 一行
    3. 删除 Modified parseEnsoUrl() 中关于 cwd 的描述和 Payload 接口中的 cwd?: string
    4. 从"Implementation Notes"中移除 activeIdscwd 查找的相关叙述,改为"会话的 cwd 从 store 中取用,URL 无需提供"
  • ⚠️ 潜在影响:未来维护人员容易按照文档实现不存在的 cwd 处理路径,产生 bug。

Fix this →


🟡 #2 src/renderer/App/hooks/useFocusSession.ts:14-32

🏷️ 类型: 可观测性 / 用户反馈 | ⏰ 优先级: P1 - 本版本修复 | 🎯 影响范围: 用户体验

  • 🔍 问题描述:当 sessions.find(...) 返回 undefined(例如用户点击了一个过期/已删除会话的链接,或 ID 拼写错误),当前实现会静默忽略该事件,用户既看不到错误提示,也看不到控制台日志,无法判断是链接失效还是程序没有响应。
// src/renderer/App/hooks/useFocusSession.ts:14-32
export function useFocusSession({ onSwitchWorktree, onSwitchTab }: UseFocusSessionOptions) {
  useEffect(() => {
    const cleanup = window.electronAPI.app.onFocusSession((params: FocusSessionParams) => {
      const { sessionId } = params;

      const sessions = useAgentSessionsStore.getState().sessions;
      const session = sessions.find((s) => s.id === sessionId);

      if (session) {
        // Switch to the session's worktree first, then set active session (same as RunningProjectsPopover)
        onSwitchWorktree(session.cwd);
        useAgentSessionsStore.getState().setActiveId(session.cwd, sessionId);
        onSwitchTab('chat');
      }
      // ❌ else 分支完全缺失
    });

    return cleanup;
  }, [onSwitchWorktree, onSwitchTab]);
}
  • 🛠️ 解决方案:至少添加一个 console.warn 以便排查问题;更好的做法是通过 toast 给用户一个明确反馈。
if (session) {
  onSwitchWorktree(session.cwd);
  useAgentSessionsStore.getState().setActiveId(session.cwd, sessionId);
  onSwitchTab('chat');
} else {
  console.warn('[focus-session] Session not found:', sessionId);
  // 可选:addToast({ type: 'warning', title: 'Session not found', description: sessionId });
}
  • ⚠️ 潜在影响:当用户点击过期链接时,应用看起来"没反应",难以定位问题。

Fix this →


⚡ 轻微级别 (Minor) 优化建议

🔵 #3 src/main/index.ts:203-219

🏷️ 类型: 用户体验 | ⏰ 优先级: P2 - 后续版本 | 🎯 影响范围: 窗口激活

  • 🔍 问题描述sendFocusSession() 仅调用 win.focus(),如果主窗口被最小化,focus() 在多数平台上不会恢复窗口。参照同文件 second-instance 处理器(line 286-291),应先判断 isMinimized()restore()
// src/main/index.ts:204-219
function sendFocusSession(params: FocusSessionParams): void {
  const windows = BrowserWindow.getAllWindows();
  if (windows.length > 0) {
    const win = windows[0];
    win.focus();  // ⚠️ 若窗口最小化,此调用无效
    if (win.webContents.isLoading()) {
      pendingOpenPath = null;
      pendingFocusSession = params;
    } else {
      win.webContents.send(IPC_CHANNELS.APP_FOCUS_SESSION, params);
    }
  } else {
    pendingFocusSession = params;
  }
}
  • 🛠️ 解决方案:与 second-instance 保持一致:
const win = windows[0];
if (win.isMinimized()) win.restore();
win.focus();
  • ⚠️ 潜在影响:用户点击外部链接后看不到窗口弹出,体验不佳。同样的问题 sendOpenPath() 也存在,可以一并修复。

🔵 #4 src/main/index.ts:72src/main/index.ts:162-164

🏷️ 类型: 代码组织 | ⏰ 优先级: P2 - 后续版本 | 🎯 影响范围: 可读性

  • 🔍 问题描述pendingFocusSession 的类型声明 FocusSessionParams 位于文件第 162 行,而其引用在第 72 行。虽然 TypeScript 的 interface 会被提升,编译无错,但先使用后声明会影响代码可读性和跳转体验。
// src/main/index.ts:72
let pendingFocusSession: FocusSessionParams | null = null;  // ⚠️ 此处引用尚未定义的 interface
// ...
// src/main/index.ts:162-164
interface FocusSessionParams {
  sessionId: string;
}
  • 🛠️ 解决方案:将 interface FocusSessionParams 上移至靠近其他类型定义/全局变量声明的位置(约 line 70 附近),或直接将其定义为 type 并与变量声明放在一起。

  • ⚠️ 潜在影响:代码可读性与维护性。


🔵 #5 src/main/index.ts:166-184

🏷️ 类型: 可测试性 | ⏰ 优先级: P2 - 后续版本 | 🎯 影响范围: 测试覆盖

  • 🔍 问题描述:新增的 parseFocusUrl() 函数是纯函数,易于单元测试,但本次 PR 未为其添加任何测试。考虑到 URL 解析容易被特殊字符/编码问题破坏(例如 enso://focus?session=abc%20defenso://focus/ 末尾斜杠、空 session 等),建议补充单元测试以锁定行为。
// src/main/index.ts:166-184
function parseFocusUrl(url: string): FocusSessionParams | null {
  try {
    const parsed = new URL(url);
    if (parsed.protocol === 'enso:') {
      const host = parsed.host;
      const pathname = parsed.pathname;
      if (pathname === '//focus' || host === 'focus') {
        const sessionId = parsed.searchParams.get('session');
        if (sessionId) {
          return { sessionId };
        }
      }
    }
  } catch {
    // Invalid URL
  }
  return null;
}
  • 🛠️ 解决方案:建议添加以下测试用例:

    • enso://focus?session=abc{ sessionId: 'abc' }
    • enso://focusnull(无 session)
    • enso://focus?session=null(空 session)
    • https://focus?session=abcnull(协议错误)
    • enso://open?session=abcnull(host 不匹配)
    • not a urlnull
    • enso://focus?session=abc%20def{ sessionId: 'abc def' }(URL 解码)
  • ⚠️ 潜在影响:后续修改可能意外改变解析行为。


🔵 #6 src/main/index.ts:204-218 · 非对称的 pending 状态清理

🏷️ 类型: 状态一致性 | ⏰ 优先级: P2 - 后续版本 | 🎯 影响范围: 边界情况

  • 🔍 问题描述sendFocusSession 在存储 pending 时会清掉 pendingOpenPath(注释说"focus 更具体");但反过来 sendOpenPath 并不清理 pendingFocusSession。在极端场景下(比如 macOS 先收到 focus URL、再收到 open-path URL,窗口仍在加载),两个 pending 会同时存在,且 did-finish-load 会先发 openPath、再发 focusSession,顺序可能与用户预期不一致。
// src/main/index.ts:204-218
function sendFocusSession(params: FocusSessionParams): void {
  // ...
  if (win.webContents.isLoading()) {
    // Store for later - overwrite any pending path since focus is more specific
    pendingOpenPath = null;   // ✅ 清理了对称方向
    pendingFocusSession = params;
  }
  // ...
}

// 但 sendOpenPath 并未反向清理 pendingFocusSession
  • 🛠️ 解决方案:如果确认"最后到达的事件胜出"是预期策略,应在 sendOpenPath 中同样 pendingFocusSession = null;或者干脆用一个联合类型 pendingDeepLink: { type: 'path' | 'focus', value: ... } | null 来明确只存一个。

  • ⚠️ 潜在影响:边界情况下可能出现 "先切会话再切路径" 或反之的非预期顺序。


📊 修复优先级矩阵

🎯 优先级 🚨 严重 ⚠️ 中等 ⚡ 轻微 📈 总计
🔴 P0 0 - - 0 个
🟡 P1 - 2 - 2 个
🔵 P2 - - 4 4 个
📊 合计 0 2 4 6 个

🎯 合并决策建议

📋 合并评估结果

📊 评估维度 ⭐ 得分 🎯 状态 💭 说明
🔐 安全性 10/10 ✅ 通过 session ID 先查 store 再用,无注入风险
📊 代码质量 8/10 ✅ 良好 遵循已有模式,细节有优化空间
⚡ 性能影响 10/10 ✅ 无影响 轻量监听器,无额外订阅
🛠️ 功能完整性 8/10 ✅ 完整 简化后的 API 清晰合理,缺失用户反馈
🧪 测试覆盖 6/10 ⚠️ 基本 纯函数 parseFocusUrl 未覆盖测试

📈 综合评分: 42/50 分 · 等级: 良好


🚦 最终建议

建议合并 (条件性)

📝 详细理由

本次 PR 的核心改动(简化 focus URL 处理、以 session.cwd 作为权威源)方向正确,代码结构清晰,与项目已有的 APP_OPEN_PATH 处理模式高度一致,核心逻辑经验证无安全与性能问题。

🟢 无严重问题、无破坏性变更
🟡 文档与实现存在明显出入 (#1),建议本版本修复以避免后续维护混乱
🟡 缺少用户反馈机制 (#2),属于小的体验问题
🔵 其余问题均为打磨型优化,可后续处理

🎯 具体行动建议

  1. 合并前建议处理

  2. 后续版本可改进


· feat/focus-session

@J3n5en J3n5en merged commit 24dad99 into J3n5en:main Apr 11, 2026
1 check passed
@zwmmm zwmmm deleted the feat/focus-session branch April 11, 2026 13:36
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.

2 participants