Appearance
功能规格说明:最近工作目录
创建日期:2026-10-21
澄清
2026-10-21 决策
- 载体从「各端自己记」改成「这台机器记一份」:桌面端原来写自己的
userData/wave-desktop.json(按 host 分组),浏览器侧 localStorage 只属于那台手机。两者都不是「这台机器」——远控页把会话全删光后最近目录直接变空,就是这个错位的直接后果。 - 文件:
~/.wave/recent-workdirs.json,与auth.json/remote-settings.json同级(用户级,跟机器走)。结构{ "dirs": string[] },MRU 顺序,上限 10(与桌面端MAX_RECENT_WORKDIRS对齐),原子写。 - 写:谁让用户选了目录,谁就向那台机器记一条——桌面端在工作目录选择器选定目录时、远控页在新建会话成功后。恢复已有会话、agent 在会话里
cd到别处、只在目录浏览器里翻看,都不写。 - 不在会话创建路径里自动写:worktree 新建是两步(
createWorktree→initialize(workdir=工作树路径)),服务端拿到的已经是被派生的路径,判断「用户到底选了哪个目录」要靠猜;把写入点放在调用方(它手里就是用户选的那个目录)更准。 - 读:bridge 新增只读方法
getRecentWorkdirs(无入参,回这台机器的 MRU)。 - worktree:记仓库根,不记工作树路径(与列表「只列主仓库根」的既有口径一致)。
- 桌面端:按主机向那台机器读(本机 stdio 子进程 / 远端 daemon),本地那份降为「旧版机器没有这条方法」时的回退缓存。
- 远控页:活会话派生的目录在前、机器 MRU 在后,跨两段去重合并;读不到就退回现状。
- 白名单:32 → 34(读
getRecentWorkdirs+ 写rememberRecentWorkdir)。写入并没有把「能建会话」之外的能力开出去——页面本来就能在任意目录initialize,这里额外能做的只是让某个路径出现在列表里。
2026-11-03 决策(列表项按「目录名 + 父路径」两行显示,分隔符跟着那台机器)
- 条目的第一行是目录名,第二行是父路径(完整路径留在
title上),与桌面端最近打开、侧边栏分组标题同一份口径——用户看的是「哪个项目」,不是一长串路径。 - 拆分必须同时认
/与\:目录拼写跟着那台机器走,远端是 Windows 时就是系统原生的C:\Users\…(realpath的结果),而 worktree 会话那条git worktree list给的是正斜杠。只按/切时整条 Windows 路径会落进第一行——远控页「新建对话」的最近目录就是这么坏的(桌面端早前踩过同一条,所以两边现在共用packages/webview/src/utils/workdirPath.ts)。 - 浏览面板同理:面包屑逐级、
…上级、进入子目录的拼接都按当前路径的风格走(盘根C:\就是根,不显示…),否则 Windows 主机上点面包屑会落到盘根、上级也去错地方。
2026-08-05 决策(工作树路径不出现在列表里、移除要落到那台机器)
- 「worktree 记仓库根」从契约变成两侧都执行的口径:原来只在调用方手里「用户选的是哪个目录」,用户直接从目录浏览器里点进某个工作树、或历史遗留数据里已有一批工作树路径时,列表里就出现
.wave/worktrees/<name>这种条目。改为在 SDK 的~/.wave/recent-workdirs.json读写两侧各归一一次:写时目录本身是 linked worktree(.git是文件、其指向的 git 目录里commondir指向主仓库的.git)就记主仓库根;读时对每一条做同一次归一,因此本规则落地前写进去的工作树路径读出来就是仓库根,并与仓库根那条合并去重。工作树目录已被删除(会话删了,git 元数据也没了)时按 Wave 自己的布局<repoRoot>/.wave/worktrees/<name>兜底换算——否则一条只剩路径的遗留条目会永远冒充一个项目。只归一「目录本身就是工作树根」这一层——用户选的是工作树的子目录时保持原样,不向上猜(猜错会把用户选的目录换掉)。 - 移除必须落在那台机器上:桌面端最近目录项上的「从列表移除」原来只删本地回退缓存(
wave-desktop.json),机器那份文件没动,紧跟着的重读又把它带回来——这就是「删不掉」。新增只写方法forgetRecentWorkdir(参数是目录,按上面的归一口径比较,把该目录的所有写法一并删掉),桌面端移除时先问机器、成功后再删本地缓存。 - 旧版机器 / 不可达:机器没有
forgetRecentWorkdir(旧版 CLI/daemon)时降级为只清本地缓存——条目可能在下一次读取时重现,但不得报错、不得卡住界面;主机不可达时按既有约束保留条目(desktop-remote.md「远端主机不可达时不得删除会话/最近目录」)。 forgetRecentWorkdir不进远控页白名单:白名单回答的是「页面能做什么」,页面从来没有移除入口(本文件用户故事 3 场景 5:页面不能改动已有条目),放行只会扩大暴露面。桌面端走自己的 stdio/隧道通道,与白名单无关。
用户场景与测试 (必填)
用户故事:最近目录记在这台机器上(优先级:P1)
作为用手机和桌面端轮着用同一台电脑的用户,我希望「最近打开过的目录」记在这台电脑上,以便换手机、清浏览器数据、甚至把所有会话都删光之后,下次新建会话仍能一键回到最近用过的目录。
为什么是这个优先级:它是两端能共用同一份列表的前提。没有它,远控页的最近目录只能从活会话派生(listDaemonSessions),会话一删就归零——那正是这个能力被发现的原因。
独立测试:向一台机器写几条最近目录,检查 ~/.wave/recent-workdirs.json 的内容与顺序;重复写同一目录验证只保留一条并置顶;写到第 11 条验证最旧的被挤出;删光会话后验证文件内容不变;把文件内容改成非法 JSON 后验证读取按空列表返回且会话创建不受影响。
验收场景:
- 假设 用户在某目录选定并新建了会话,则 那台机器的
~/.wave/recent-workdirs.json中该目录被置顶;已存在的同名条目只保留一条(比较前去掉尾部分隔符,/repo与/repo/视为同一个) - 假设 列表已有 10 条,则 新的写入把最旧的一条挤掉,文件最多保留 10 条
- 假设 用户建的(或直接选定的)目录是一个 linked worktree,则 记入的是仓库根目录,工作树路径不进入列表;当 文件里已经存在工作树路径的条目(规则落地前的历史数据),则 读取时必须显示为它对应的仓库根,并与仓库根那条按路径去重合并为一条;当 该工作树目录已被删除(
git元数据随之消失),则 按<repoRoot>/.wave/worktrees/<name>布局换算仓库根;假设 用户选的是工作树内的子目录,则 按用户所选原样记录(只归一「目录本身就是工作树根」这一层,不向上猜) - 假设 用户恢复一条已有会话、在会话里用 bash
cd到别处、或只在目录浏览器里翻看而没有选定,则 列表不变 - 假设 文件不存在、内容不是合法 JSON、或目录不可写,则 读取按空列表处理、写入静默失败;会话创建与其余功能不受影响,调用方不因它报错
- 假设 同一台机器上多个进程(daemon / 桌面端子进程 / CLI)先后写入,则 每次写入都以「读现状 → 合并 → 原子替换」完成;并发下允许丢掉一次最近更新,不允许文件损坏成不可解析
用户故事:桌面端按主机读那台机器的列表(优先级:P1)
作为桌面端用户,我希望切换主机时看到的最近打开目录就是那台机器自己的记录,以便它和手机端(远控页)看到的是同一份,而不是桌面端在一个本地文件里替所有主机攒出来的副本。
为什么是这个优先级:不切的话两边会长期分叉——同一台电脑,桌面端本机组一份、远控页一份,用户会在两端看到不同的「最近打开」。桌面端既有的行为契约(只由用户主动选择更新、启动取首项、切主机取该主机首项、工作树路径不入列表)一条都不变,变的只是数据从哪来。
独立测试:选一台主机打开工作目录选择器,验证列表与该主机 ~/.wave/recent-workdirs.json 一致;在同一台机器上用远控页新建一个会话,回到桌面端该主机验证目录已出现;把该主机的方法挡掉(模拟旧版机器)验证回退到本地缓存且仍可继续工作;恢复一条历史会话验证最近打开列表顺序不变。
验收场景:
- 假设 用户选中某台主机,则 最近打开列表来自那台机器(
getRecentWorkdirs走该主机的通道:本机是 stdio 子进程、远端是隧道里的 daemon);同一路径在另一台主机上不出现 - 假设 用户用远控页在这台机器上新建过会话,则 桌面端该主机的最近打开能看到那个目录;反之桌面端选过的目录,远控页新建会话时也能看到(两端同一份)
- 假设 该主机的 version 旧到没有
getRecentWorkdirs,则 桌面端回退到本地wave-desktop.json里那个主机的分组,不得显示空列表或报错;此后该主机的本地分组仍按用户的选择继续更新(否则回退列表再也不长) - 假设 用户在桌面端选定目录(本机 OS 对话框 / 远端目录浏览面板 / worktree 会话用到的仓库根),则 向那台机器记一条(
rememberRecentWorkdir),同时继续更新本地缓存那份回退数据 - 假设 用户恢复历史会话、或 agent 工作目录在会话中被 bash 改写,则 不写入(既有契约不变,见 desktop-sessions.md 场景 9)
- 假设 用户点最近打开条目上的「从列表移除」,则 桌面端必须先把这项目录从那台机器的
~/.wave/recent-workdirs.json里删掉(forgetRecentWorkdir,按归一口径比较:工作树路径与仓库根同属一项),再删本地回退缓存并刷新列表,刷新后该条目不得重新出现;当 该主机的版本没有forgetRecentWorkdir,则 降级为只删本地缓存(条目可能在下一次读取时重现),不报错、不卡界面;当 主机不可达,则 保留该条目,仅提示无法连接主机
用户故事:远控页新建会话时列出机器侧最近目录(优先级:P1)
作为用手机发起新会话的用户,我希望「新建会话」里除了活会话的目录,还能看到这台电脑最近用过的目录,以便删光了所有会话、或者想开一个之前用过但眼下没有会话的目录时,不必每次都从「浏览…」一级级点进去。
为什么是这个优先级:这是这份机器级记录的第一个直接受益者,也是「会话全删光后最近目录变空」这个具体缺陷的修法。
独立测试:在机器上先建立过若干目录的会话,把手机端的会话全部删除,再打开「新建会话」,验证最近目录仍列出那些目录;把 getRecentWorkdirs 挡掉(模拟旧版电脑)验证退回只列活会话目录且不报错。
验收场景:
- 假设 用户打开「新建会话」,则 最近目录是两段合并的结果:活会话派生的目录在前(
repoRoot ?? workdir,按最近活跃排序,口径见 remote-web.md 本故事场景 2),机器 MRU 在后,两段之间按路径去重 - 假设 手机端把会话全部删光(活会话为 0),则 最近目录仍列出机器 MRU,不再只剩「浏览…」
- 假设 电脑侧是旧版本(
getRecentWorkdirs不可用),则 退回只列活会话派生的目录(本能力引入前的行为),不得因此报错或把弹窗置成空 - 假设 列表里的某个目录已经在机器上被删除,则 读取时不过滤(不逐条 stat);用户点选后创建失败按既有错误路径提示
- 假设 有人问「页面能不能直接改写这份列表」,则 不能:写方法
rememberRecentWorkdir只在新建会话成功后由页面调用一次,参数是那次创建用的目录;页面没有其它入口改动它,也不能借它删改已有条目 - 假设 那台电脑是 Windows(目录是
C:\Users\…这种系统原生拼写),则 每个最近目录条目仍显示为「目录名(第一行)+ 父路径(第二行)」,完整路径只在title上;「浏览…」面板的面包屑逐级可点、…上级与进入子目录都按反斜杠拼接(C:\就是根)——不得把整条路径当成一段显示,也不得把用户带到一个拼错的路径上(拆分与拼接规则见 2026-11-03 决策,两侧共用同一份实现)