v0.5.2 Moonlight Dialogs · 弹窗系统重设计

Quiet,moonlitdialogs.

每一次打断,都安静、清楚、可预期。

alune 的 29 个模态框、7 个气泡确认、4 个浮层菜单与反馈中心,统一为一套「月光」弹窗语言:双层玻璃外壳、按风险分级的确认、键盘优先的操作路径,以及只动 transform 与 opacity 的弹性动效。

浏览设计稿
29模态框
07气泡确认
04浮层菜单
L0–2确认等级

向下滚动,逐层查看

01 Principles

一次打断,只问一件事Six rules every dialog in alune follows.

现有弹窗来自 antd 默认样式、手写 Popconfirm 与原生 dialog 三套来源,标题、按钮文案和危险程度各不相同。重设计先统一规则,再统一外观。

规则 1verb-first

按钮写后果,
不写「确定」

标题提出问题,说明讲清代价,按钮直接给出答案。用户只读按钮,也能知道点下去会发生什么。

确定 OK 丢弃改动 删除分支 创建并切换 推送并设置上游

Esc 取消不可撤销

规则 2L0 · L1 · L2

确认强度与风险成正比

可撤销的操作不打断;有代价的用气泡确认;不可撤销的必须勾选或输入后才能提交。

L0直接执行可撤销
L1二次确认有代价
L2显式确认不可撤销

规则 3double bezel

深度来自结构,而非阴影

6px 半透明外壳托住实心内核,再加一圈色调光晕。弹窗像一块放在托盘里的玻璃,而不是一张投了重阴影的纸。

规则 4keyboard

键盘可以走完全程

Esc 关闭,↵ 提交,多行输入中用 ⌘↵。L2 默认聚焦「取消」,并禁用回车直接提交。

规则 5rhythm

动效有重量,也有节制

遮罩先淡入,外壳随弹簧曲线升起,内容以 40ms 错峰。退出更快,永远不挡下一步操作。

规则 6one skin

一处定义,处处一致

所有 Modal、Popconfirm、Popover 与原生 dialog 共用同一套令牌和外壳。新增弹窗只写内容,不再单独写样式。

02 Tokens

月光色不变,层次重排Palette, depth, radii, type and motion.

保留 alune 的蓝 / 靛月光色系与现有主题令牌,只新增弹窗专用的外壳、内核、遮罩与光晕层。浅色与深色逐一配对。

色彩light / dark

令牌浅色深色
月光靛--moon-indigo#6e86e8#6e86e8
月光青--moon-cyan#8fe3ee#8fe3ee
主操作--blue#415ab0#afc6ff
危险--red#ad3b49#ffb7c0
警示--orange#995413#ffd096
成功--green#0f6d56#b0e5cc
内核--dlg-core-bg#ffffff#223049
遮罩--dlg-scrim22 32 62 / 28%4 8 20 / 52%

层级e0 → e3

字段
e0 · 内嵌
浮层
e1 · Popover
外壳
e2 · 双层外壳
模态
e3 · 遮罩 + 光晕

阴影只用低不透明度的蓝黑色,深度主要来自外壳描边与遮罩后的光晕。

圆角concentric

r(core) = r(shell) − shell-pad(6)

字体Geist · PingFang SC · Geist Mono

丢弃 3 个文件的改动?title · 17 / 600 / −0.01em
未暂存的修改会被还原为暂存区内容,无法从 alune 找回。body · 13.5 / 400 / 1.65
新的本地分支名称label · 12.5 / 500
分支alune · mainL1eyebrow · 11 / 500 · pill
feature/moonlight-dialogsmono · 12.5 / 400

缓动no linear · no ease-in-out

Spring · 外壳进场 cubic-bezier(.32,.72,0,1)
360ms
Glide · 浮层展开 cubic-bezier(.16,1,.3,1)
200ms
Exit · 退出 cubic-bezier(.4,0,1,1)
160ms

尺寸--w + shell-pad × 2

sm400
md520
lg680
xl880

单字段用 sm,常规表单用 md,列表与结果用 lg,差异预览用 xl。窗口不足时一律收为 100%,560px 以下改为底部抽屉。

03 Anatomy

三段式结构,九个固定部件Head, body, foot — nothing floats free.

所有模态框都由同一副骨架组成:头部说明「是什么」,正文说明「会怎样」,底栏给出「怎么做」。打开红线可查看间距。

1 2 3 4 5 6 7 8 9 532 = 520 + 6 × 2 r 22 / 16 body 20 foot ≥ 60

推送alune · feature/dialogsL0

推送并设置上游分支

当前分支还没有上游。选择远程后,将以 -u 推送并建立跟踪关系。

之后可直接拉取和推送,不必再次选择远程。

↵ 推送Esc 取消

  1. 1
    外壳 Shell6px 半透明托盘,承担描边与阴影。.dlg-shell
  2. 2
    内核 Core实心内容面,顶部 1px 色调高光,背后是色调光晕。.dlg-core
  3. 3
    图标40px 圆角方块,细线图标,颜色跟随风险色调。
  4. 4
    眉标对象类型 + 仓库 / 分支上下文,L1/L2 在分隔线后注明等级。
  5. 5
    标题与说明标题是问题或动作;说明一句讲清后果,引用名称用等宽字。
  6. 6
    关闭30px 圆形按钮,忙碌时禁用,与 Esc 等价。
  7. 7
    正文字段、提示与列表按 16px 节奏排布,正文左右留白 20px。
  8. 8
    快捷键提示底栏左侧告诉用户键盘路径;768px 以下隐藏。
  9. 9
    操作区次要在左、主操作在右;主按钮带圆形箭头,危险操作使用红色。

04 Levels

先判断能否撤销,再决定打断多少Reversibility decides the pattern.

现有代码中「删除分支」用 Popconfirm,「丢弃改动」用 Modal + 勾选,「删除令牌」用 modal.confirm,强度并不对应风险。统一为三级后,形态、颜色和键盘行为都由等级决定。

L0

可撤销 · 直接执行

结果可见、可回退。表单弹窗只负责收集信息,不再额外确认。

  • 新建分支、重命名、创建储藏
  • 添加远程、设置作者、设置上游
  • 打开仓库、扫描目录

蓝色主按钮 · ↵ 直接提交

L1

有代价 · 二次确认

可以恢复,但需要额外操作或会影响他处。用气泡确认,或说明影响范围的小弹窗。

  • 删除已合并分支、删除储藏
  • 移除仓库登记、删除连接、删除令牌
  • 本地忽略目录、跳过部分暂存

柔和红按钮 · 聚焦主操作 · 需二次点击

L2

不可撤销 · 显式确认

数据会永久丢失或覆盖远程历史。必须勾选或输入名称,才能启用主操作。

  • 丢弃改动、删除未跟踪的新文件
  • 强制推送、关闭 PR/MR
  • 删除 Worktree 及其未提交改动

实心红按钮 · 默认聚焦「取消」 · 禁用 ↵

现状 → 新形态

inventory · 40 surfaces

按真实代码整理的对照表。「形态」列是重设计后的呈现方式,「来源」是当前实现位置。

场景现状新形态等级主操作键盘
新建 / 重命名分支BranchesView · BranchPickerModal,按钮「确定」sm 表单,实时校验L0创建并切换 / 重命名↵
本地分支名称冲突useBranchSwitchmodal.confirm + Radio选项卡片 + 条件输入L0继续↵
合并分支BranchesViewPopconfirm气泡确认,提示冲突L1合并↵
删除分支 / 储藏BranchesView · StashesViewPopconfirm气泡确认,柔和红L1删除分支 / 删除储藏↵
删除令牌 / 模型AccessTokenSettings · ProviderSettingsmodal.confirmsm 弹窗,列出影响仓库L1删除令牌↵
丢弃改动DiscardChangesDialog · ChangesViewModal + Checkbox放弃 / 保留对账 + 勾选确认L2放弃全部并删除(N)聚焦取消
删除新文件DeleteNewFileDialogModal文件清单 + 勾选确认L2删除文件聚焦取消
强制推送RepositoryDetailPagemodal.confirm提交对比 + 输入分支名L2强制推送聚焦取消
合并 / 关闭 PR/MRPullRequestActionsModal,关闭时 danger合并方式分段;关闭为 L2L1 L2确认合并 / 确认关闭↵ / 聚焦取消
通知、分支、Worktree 浮层BranchPicker · WorktreesPanelPopover / Dropdown双层浮层,可筛选——↑↓ ↵
反馈中心Feedback原生 dialog + 托盘托盘 + 收件箱—复制诊断信息Esc

05 Form dialogs

收集信息,不制造焦虑13 artboards · L0 · Enter to submit

表单弹窗全部属于 L0:字段就地校验,主按钮在信息完整前保持禁用,提交中以月相加载替代转圈。每张画板右上角可以切换状态,或点击「实时预览」在真实遮罩中操作。

F01

添加 SSH 连接

md · 520ConnectionsPageL0

连接SSH

添加 SSH 连接

保存后可以在这台主机上扫描和打开远程仓库。

请输入主机地址

认证方式

使用本机 SSH Agent 中已加载的密钥,alune 不保存任何凭据。

↵ 保存Esc 取消

ConnectionsPage.tsx:182
  • 结构认证方式由下拉框改为 三张选项卡片,切换后只展开对应字段,弹窗高度变化由弹簧曲线过渡。
  • 无障碍选项卡片是 radiogroup,方向键切换;密码字段的显示按钮会同步 aria-pressed 与标签。
  • 可选在底栏左侧增加「测试连接」,保存前验证主机可达。
F02

打开本地仓库

md · 520OpenLocalRepositoryL0

仓库本机

打开本地仓库

选择已有 Git 仓库,或粘贴本机的完整目录路径。Git 操作在运行 alune 的电脑上执行。

也可以直接粘贴 Windows 路径,例如 C:\Projects\repository

正在读取仓库状态、分支与远程…

alune/Users/clean/Desktop/alune
本机
main当前分支
3项改动
1个远程

尚未配置提交作者,可以在工作区中补充。

↵ 检查 · 打开Esc 取消

OpenLocalRepository.tsx:71
  • 流程「检查仓库」与「打开工作区」分成两步:检查结果以 仓库卡片 呈现,确认无误后主按钮才亮起。
  • 错误错误就地显示在字段下方,不再弹出第二层提示;修改路径后自动回到「空」状态。
F03

扫描远程目录

lg · 680RepositoriesPageL0

仓库SSH · 构建服务器

扫描远程目录

从指定路径向下搜索最多四层,查找包含 .git 目录的文件夹。

执行扫描后,将在这里列出发现的仓库。

在 /home/developer 下没有找到 Git 仓库。可以换一个上级目录,或检查此用户的读取权限。

正在搜索第 2 / 4 层 · 已检查 184 个目录

发现 4 个仓库/home/developer

alune/home/developer/code/alune
已添加
moonlight-api/home/developer/code/moonlight-api
infra/home/developer/ops/infra
dotfiles/home/developer/dotfiles

逐个添加,无需关闭弹窗Esc 关闭

RepositoriesPage.tsx:389
  • 结构原来的弹窗没有底栏,现在补上「完成」和快捷键提示,并用 空状态月相 替代空白。
  • 状态扫描中显示层数和已检查目录数,比无限旋转更容易判断还要等多久。
  • 可选结果多于 8 个时,在列表上方加入筛选框,复用 pop-search 样式。
F04

新建 / 重命名分支

sm · 400BranchesView · BranchPickerL0

分支alune

新建分支重命名分支 feature/login

从当前 HEAD main · 6a35428 创建,并立即切换

名称未变化

名称可用,跟踪关系保持不变

请输入分支名称

↵ 确认

BranchesView.tsx:231 · :242
  • 文案标题直接引用分支名;名称未变化时主按钮保持禁用,避免一次无效提交。
  • 可选输入空格时自动替换为 -,并在提示里说明。
F05

本地分支名称冲突

md · 520useBranchSwitchL0

分支origin/feature/login

本地分支名称冲突

本地已有同名分支 feature/login。请选择如何检出远程分支。

请输入新的本地分支名称

↑↓ 选择↵ 继续

useBranchSwitch.tsx
  • 结构Radio 改为两张选项卡片,只有选中「使用新名称」时才出现输入框。
  • 无障碍原先提交后才报错;现在留空时主按钮禁用,并在字段下方给出提示。
F06

储藏当前改动

sm · 400StashesViewL0

储藏alune · main

储藏当前改动

保存改动并还原工作区,之后可以随时恢复。

将储藏 7 项已跟踪改动

将储藏 10 项改动,包括 3 个未跟踪文件

↵ 创建

StashesView.tsx:190
  • 文案复选框括号里的补充说明拆成副标题;下方实时显示本次会储藏多少项。
F07

添加远程

sm · 400RemotesViewL0

远程本机 · aluneSSH · 构建服务器

添加远程

使用本机的 Git 凭据、SSH Agent 和 known_hosts 配置同步。

同步在远程主机上执行,使用该主机的 Git 认证配置。

↵ 添加

RemotesView.tsx:123
  • 结构运行位置写进眉标,说明文字只讲认证方式;地址下方新增 前缀快捷片,点击会替换已有前缀。
F08

设置提交作者

sm · 400RepositoryContextNoticeL0

提交alune

设置提交作者

只写入此仓库的 Git 配置,不修改系统全局配置。

填写后,提交记录会显示为 clean <邮箱>

将写入 .git/config 的 [user] 段

↵ 保存

RepositoryContextNotice.tsx:138
  • 文案主按钮写明作用范围「保存到此仓库」;写入位置在字段提示中说明,不再单独放一段说明文字。
F09

设置上游 · 选择拉取来源

md · 520RepositoryDetailPageL0

推送拉取alune · feature/dialogs

设置上游并推送选择拉取来源

当前分支还没有上游。确认远程和分支后执行。

feature/dialogs → origin/feature/dialogs origin/feature/dialogs → feature/dialogs -u --ff-only

↵ 执行Esc 取消

RepositoryDetailPage.tsx:459
  • 结构新增 方向预览条,用箭头写清本地与远程的关系,并标出实际使用的 Git 参数。
F10

新建本地 Worktree

md · 520WorktreesPanelL0

Worktreealune · main

新建本地 Worktree

从当前 HEAD 创建新分支和独立的工作目录。请选择当前仓库之外的目录。

此目录位于当前仓库内,请选择仓库之外的位置

alune-dialogs新建
/Users/clean/Desktop/alune-dialogs feature/dialogs · 基于 main 6a35428

↵ 创建

WorktreesPanel.tsx:254
  • 结构底部用 Worktree 列表的同款行样式 预览创建结果,与浮层中的列表保持一致。
  • 错误目录位于仓库内时就地提示并禁用提交,不用等到创建失败。
F11

新增 / 编辑令牌

md · 520AccessTokenSettingsL0

设置访问令牌

新增令牌编辑令牌

这里只保存配置;实际访问权限会在仓库读取 PR/MR 时检查。

令牌值 已保存
••••••••••••••••

保存后,3 个仓库远端关联的新请求会使用新值。

↵ 保存

AccessTokenSettings.tsx:259
  • 结构编辑时令牌值默认 锁定,点击「更换令牌值」后才解锁输入,避免误改。
  • 无障碍显示/隐藏按钮会同步切换 aria-label 与 aria-pressed。
F12

手动添加 / 编辑模型

md · 520ProviderSettingsL0

设置模型服务商 · OpenAI 兼容

手动添加模型编辑模型

点击「保存配置」后生效。显示名称留空时使用模型 ID。

此模型 ID 已存在

必须与服务商文档一致,不能包含空格

这是当前默认提交模型

更改 ID 并保存配置后,需要在「提交生成」中重新选择默认模型。

↵ 确认

ProviderSettings.tsx:567
  • 文案「保存配置后生效」提到说明里,避免用户误以为点「添加模型」就已保存。
F13

确认合并 PR/MR

md · 520PullRequestActionsL1

PRShaoClean/Alune · #134

确认合并 PR/MR

fix(settings): 复用工作区外壳并统一设置控件与响应式布局

目标分支:developmentfeature/settings-shell → development
检查通过 检查中
合并方式

保留全部 5 个提交,并在 development 上创建一个合并提交

将 5 个提交压缩为一个提交写入 development

把 5 个提交逐个变基到 development,不创建合并提交

提交时会再次核对代码版本与合并条件,最终由托管平台执行权限、检查和分支保护规则。

分支保护规则尚未满足

需要 1 项必需检查通过后才能合并。

↵ 合并

PullRequestActions.tsx:227
  • 结构合并方式由下拉框改为 分段控件,每种方式下方用一句话说明结果。
  • 风险分支保护未满足时,说明原因并禁用主按钮,而不是让托管平台返回报错。

06 Destructive

先说清后果,再交出按钮10 artboards · L1 / L2 · cancel owns the focus

危险确认统一为「将放弃 / 将保留」对账单:用户先看到数量和范围,再看到按钮。L2 弹窗默认聚焦「取消」,↵ 不会触发主操作,必须显式勾选或输入名称。

D01

放弃所有更改

md · 520DiscardChangesDialogL2

工作区alune · mainL2 · 不可撤销

放弃所有更改?

作用于整个当前仓库 / worktree,不受文件列表筛选影响。其他仓库和 worktree 不受影响。

正在核验整个仓库的改动…

alune/Users/clean/Desktop/alune
9已跟踪 · 恢复
3未跟踪 · 删除
3未跟踪 · 保留
5已暂存 · 保留

将放弃

  • 9 个已跟踪文件的未暂存改动
  • 3 个未跟踪文件,直接删除

将保留

  • 全部已暂存内容,含 2 个新文件
  • 3 个未跟踪文件
  • 忽略文件
  • 跳过 1 个子模块

当前没有可放弃的未暂存更改。

默认聚焦「取消」↵ 已停用

DiscardChangesDialog.tsx:99
  • 结构三段说明文字改为 数字 + 对账单:勾选「同时删除」时,未跟踪文件会从「将保留」移到「将放弃」,按钮计数同步变化。
  • 设计变更新增「我了解本次影响 N 个文件」勾选;现有实现只靠按钮上的计数提示,不符合 L2 显式确认规则。
  • 无障碍role=alertdialog,打开时聚焦「取消」;执行中锁定关闭按钮、Esc 和遮罩点击,与现有 pending 逻辑一致。
D02

删除整个新增文件

md · 520DeleteNewFileDialogL2

SSH构建服务器 · aluneL2 · 不可撤销

删除整个新增文件?

DialogShell.tsxapps/web/src/components/DialogShell.tsx
… U AM AD

正在核验远端文件和暂存内容…

将从远端磁盘删除此新增文件。

此新文件同时存在已暂存和未暂存改动,删除范围包含两部分的全部内容。

远端磁盘文件已不存在。本次将清理此路径残留的暂存新增内容。

直接删除,不会进入回收站。此操作不可撤销。

不可撤销

DeleteNewFileDialog.tsx:82
  • 结构目标路径改为 文件行 + 状态徽标(U / AM / AD),核验结果只替换一句说明,其余部分保持不动。
  • 设计变更补充显式勾选,与 D01 保持同一套 L2 规则。
D03

强制推送当前分支

md · 520RepositoryDetailPageL2

本机alune · feature/dialogsL2 · 覆盖远程

强制推送当前分支?

将更新远程历史,覆盖上次获取后已知的远程提交。

远程将丢失

  • 4e1f0a2调整弹窗间距
  • b83c9d1临时修复

将推送

  • c2d7e45弹窗外壳
  • 9a01b3f危险确认
  • 77f5e0c动效令牌

使用 --force-with-lease:若远程又有变化,Git 会拒绝此次推送。

与当前分支名不一致

分支名一致

覆盖远程历史

RepositoryDetailPage.tsx:323
  • 结构把「覆盖远程提交」变成可见的 提交对比:左侧是会从远程消失的提交,右侧是本次推送的提交。
  • 设计变更新增输入分支名确认;现有 modal.confirm 只要点一次按钮。
D04

删除 Worktree 目录

md · 520WorktreesPanelL2

WorktreealuneL2 · 删除磁盘目录

删除 Worktree 目录?

alune-dialogs
/Users/clean/Desktop/alune-dialogs feature/dialogs
干净 3 项改动

将删除

  • 磁盘上的工作目录
  • 仓库中的 Worktree 登记

将保留

  • 分支 feature/dialogs
  • 已提交的全部内容
此目录有 2 项改动和 1 个未跟踪文件

存在改动、未跟踪或忽略文件时,Git 会拒绝删除。请先在此 Worktree 中提交或储藏。

不可撤销

WorktreesPanel.tsx:291
  • 结构「保留分支」原来藏在长句里,现在单独列在 将保留 一栏。
  • 预检打开时先读取目录状态;有改动时直接说明原因并提供出口,不再让 Git 报错。
D05

删除令牌

md · 520AccessTokenSettingsL1

设置访问令牌L1

删除令牌“公司 GitLab”?

只删除 alune 保存的配置,不会在 GitHub / GitLab 撤销令牌。

以下仓库远端将解除关联3

aluneorigin · gitlab.company.cn
moonlight-apiorigin · gitlab.company.cn
infraupstream · gitlab.company.cn

删除后,这些远端需要重新选择令牌才能读取 PR/MR。

尚未关联仓库,删除不会影响任何远端。

↵ 删除Esc 取消

AccessTokenSettings.tsx:350
  • 等级令牌可以重新添加,属于 L1:柔和红按钮、聚焦主操作、↵ 可确认。
  • 文案「不会撤销令牌」上移到说明,避免用户误以为删除后令牌就失效了。
D06

删除模型

sm · 400ProviderSettingsL1

设置模型服务商L1

删除模型?

确定从此服务商移除“Sonnet 5.5”(claude-sonnet-5-5)吗?点击「保存配置」后生效。

这是当前默认提交模型

删除并保存配置后,需要在「提交生成」中重新选择默认模型。

↵ 删除

ProviderSettings.tsx:617
  • 结构普通模型只有头部和底栏,是最小的确认弹窗;默认模型才追加警告块。
D07

离开前保存代理修改

md · 520ProxySettingsL1

设置网络代理 · 2 项修改

保存网络代理修改?

离开前保存配置,或放弃这次修改。已有 SSH 工作不会被保存操作中断。

HTTP 代理
http://127.0.0.1:7890 → http://127.0.0.1:7897
不代理
localhost, 127.0.0.1, *.internal
ProxySettings.tsx:626
  • 结构三个按钮用三种强度区分:文字按钮 返回、柔和红 放弃、主按钮 保存;并列出具体改了什么。
  • 错误配置无效时禁用「保存并离开」,只留下「继续编辑」和「放弃」两条路。
D08

在本地忽略此目录

md · 520ChangesViewL1

改动alune · 未跟踪目录L1

在本地忽略此目录?

目录及其内容会从当前仓库的未跟踪列表中隐藏,磁盘文件会保留。

.git/info/exclude+1
66 # git ls-files --others --exclude-from=.git/info/exclude
7+/.playwright-cache/

写入 Git 本地忽略文件,对同一仓库的 Worktree 生效,不会修改团队的 .gitignore。

↵ 忽略

ChangesView.tsx:196
  • 结构用一段 规则差异 直接展示会写入什么,比文字描述「写入本地忽略文件」更具体。
  • 等级L1 但不具破坏性,因此用主按钮而不是红色按钮。颜色只表示风险,不表示等级。
D09

暂存可处理的改动

md · 520ChangesViewL1

改动alune · mainL1

暂存 10 项改动?

将跳过以下 2 项,请在对应仓库中处理。

10将暂存
2跳过
vendor/sdk子模块 · 有独立的 Git 状态
examples/demo嵌套仓库 · 有独立的 Git 状态

↵ 暂存

ChangesView.tsx:143
  • 结构被跳过的项目带上类型和「打开仓库」出口,用户不需要记住路径再去找。
D10

确认关闭 PR/MR

md · 520PullRequestActionsL2

PRShaoClean/Alune · #133L2

确认关闭 PR/MR

关闭会结束此 PR/MR,代码不会合入目标分支。

feat(repository): 支持 PR/MR 评论、合并与关闭feature/pr-actions → development

默认聚焦「取消」

PullRequestActions.tsx:227
  • 等级关闭可以在平台上重新打开,但会通知评审者并中断讨论,因此仍归为 L2,并在勾选项里说明可恢复的途径。
  • 错误失败时保留弹窗,把平台原因写进警告块,主按钮改为「重试关闭」。

07 Info & panels

只读,但不只是一段文字5 artboards · L0 · Esc to dismiss

信息类弹窗不要求决定,只负责把上下文讲清楚:路径、归属和下一步入口。状态直接写进标题,用户扫一眼就知道现在处于哪一步。

I01

改动目录详情

md · 520ChangesViewL0

改动alune · 未跟踪

Worktree 详情嵌套仓库详情子模块详情目录详情

alune-reviewexamples/demovendor/sdk.playwright-cache /Users/clean/Desktop/alune/alune-review/ /Users/clean/Desktop/alune/examples/demo/ /Users/clean/Desktop/alune/vendor/sdk/ /Users/clean/Desktop/alune/.playwright-cache/
当前分支
review/pr-133main分离 HEAD · 3f9a2c1
改动
2 项未暂存干净提交指针已变化

此目录有独立的 Git 状态。请打开对应仓库查看改动;当前仓库的批量操作会跳过此目录。

此路径是目录,无法作为单个文件预览、暂存或删除。请在文件树中查看内容并单独处理。

需要移除此工作目录时,请使用 Worktree 管理中的移除操作。

Esc 关闭

ChangesView.tsx:477
  • 结构四种目录共用一个模板,只替换图标、标题和一句说明;按钮集合与 changeActions() 一致,子模块不能本地忽略。
  • 可选分支和改动摘要需要额外读取一次嵌套仓库状态;读取失败时整行隐藏,不阻塞弹窗。
I02

令牌的仓库关联

md · 520AccessTokenSettingsL0

设置访问令牌 · GitLab

“公司 GitLab”的仓库关联

以下仓库远端使用此令牌读取和操作 PR/MR。

alune · origingitlab.company.cn/platform/alune
本机
moonlight-api · origingitlab.company.cn/platform/moonlight-api
SSH
infra · upstreamgitlab.company.cn/ops/infra
SSH

尚未关联仓库。

Esc 关闭

AccessTokenSettings.tsx:385
  • 结构「仓库 · 远端」加粗,托管路径作为第二行;右侧徽标区分本机和 SSH 仓库。
  • 一致与 D05 删除令牌共用同一个列表组件,删除时看到的就是这里的内容。
I03

储藏差异

xl · 880StashesViewL0

储藏alune · main · 2 小时前

储藏差异 · stash@{0}

On main: 弹窗外壳草稿

3 个文件+18−6
apps/web/src/components/DialogShell.tsx+12−3
@@ -18,9 +18,18 @@ export function DialogShell({ level, tone, children }: Props) {
1818 const titleId = useId();
19- return <Modal title={title} {...rest}>{children}</Modal>;
19+ const critical = level === 2 || tone === 'danger';
20+ return (
21+ <Modal
22+ role={critical ? 'alertdialog' : 'dialog'}
23+ keyboard={!critical || !pending}
24+ modalRender={(node) => <Bezel tone={tone}>{node}</Bezel>}
2025 >
apps/web/src/styles/dialog.css+6−3
@@ -4,6 +4,9 @@ .ant-modal-content {
4- border-radius: 8px;
4+ border-radius: calc(var(--dlg-radius) - var(--shell-pad));
5+ box-shadow: 0 0 0 1px var(--hair), inset 0 1px 0 var(--dlg-core-hi);
apps/web/src/components/Bezel.tsx新文件

此储藏没有文本差异。

Esc 关闭只读预览

StashesView.tsx:181
  • 结构原来的 <pre> 纯文本改为 按文件分组的差异:文件头吸附显示增删计数,统一复用 DiffViewer 的行号与色板。
  • 可选超过 400 行时折叠后续文件,只展示文件头和计数,点开后再渲染。
  • 无障碍增删行除颜色外保留 +/- 符号列,色弱用户同样能分辨。
I04

版本更新

md · 560UpdatePanelL0

设置版本更新 · GitHub Releases

正在检查 GitHub Releases…当前已是最新稳定版本发现新版本正在下载安装包…安装包已下载并通过校验更新未完成

0.5.2当前版本
…最新版本
0.5.2最新版本 · 一致
0.6.0最新版本

38.6 MB / 96.4 MB · 6.1 MB/s

更新说明v0.6.0 · 2026-09-28

  • 新增弹窗统一为双层外壳,危险操作分为 L0 / L1 / L2 三级确认
  • 新增强制推送前显示远程将丢失的提交
  • 改进储藏差异按文件分组,并显示增删计数
  • 修复从设置返回后,工作区侧栏的焦点未恢复

下载完成后,点击「重启安装」将关闭当前 SSH 连接,安装新版本并重新打开应用。普通退出不会自动安装。

Esc 关闭后台继续下载

UpdatePanel.tsx:33
  • 结构状态文字 直接作为标题,并加 aria-live;操作按钮从正文移到底栏,每个状态只保留一个主操作。
  • 动效进度条用 translateX 推进,不改宽度;检查中显示流光,不显示虚假的百分比。
  • 预告重启安装会断开 SSH,这条提示在下载前就出现,而不是点击后才弹出。
I05

布局设置

sm · 420LayoutSettingsL0

工作区此设备

布局设置

侧边面板

显示左侧工作区连接与仓库 · ⌘ B
显示右侧面板改动、提交与历史 · ⌘ ⇧ B

面板宽度窗口较小时自动适配

差异视图也可在差异工具栏切换

即时生效 · 此设备保存

LayoutSettings.tsx:7
  • 结构三组设置用小标题分隔;面板隐藏时,对应的宽度滑杆随之禁用,避免调整看不到的东西。
  • 等级设置即时生效,没有「保存」,所以「完成」只是关闭,用幽灵按钮即可。
  • 可选「恢复默认」保留工作区树的展开与排序,这句说明移到按钮的 tooltip。

08 Popconfirm & layers

轻量确认,留在原地7 popconfirms · 4 popovers · 2 menus · no scrim

气泡确认只用于影响单个对象、且可以说清后果的操作;一旦涉及多个对象或不可撤销,就升级为 L2 弹窗。浮层和弹窗用同一套双层外壳,但不加遮罩、不锁滚动。Esc 或点击外部都会关闭浮层,并把焦点还给触发按钮。

气泡确认

Popconfirm · 320 px · anchored

标题直接写出对象名,说明只写一句后果,确认按钮写动作本身。只有影响单个文件的丢弃仍用气泡,但需要先勾选确认。

P01

删除分支

L1BranchesView

删除

删除“feature/login”?

已合并到 main,删除后可从 main 的历史中找回提交。

有 2 个提交尚未合并,Git 会拒绝删除。请先合并,或在分支页使用强制删除。

已合并到 main 未合并 · 2 个提交
BranchesView.tsx:155
  • 预检打开气泡时先读取合并状态。未合并时直接说明原因并禁用按钮,不必等 Git 报错。
P02

合并分支

L1BranchesView

合并

将“feature/login”合并到 main?

将合并 3 个提交。如有冲突,会停在冲突状态,可以随时中止。

+3 提交 工作区干净 4 项未提交改动

建议先储藏

BranchesView.tsx:118
  • 改写原文案「请先保存当前改动」改为读取工作区后给出实际数量,干净时不再提示。
P03

删除储藏

L1StashesView

删除 stash@{1}?

“On main: 弹窗外壳草稿”包含 3 个文件的改动,删除后无法恢复。

StashesView.tsx:162
  • 改写「删除此储藏?」改为写出编号和说明,列表里有多条储藏时不会删错。
P04

丢弃单个文件

L1 · 需勾选ChangesView

丢弃

丢弃此文件的未暂存改动?

恢复为暂存区的内容,已暂存的改动保留。此操作不可撤销。

apps/web/src/components/Feedback.tsx +12 −4

ChangesView.tsx:371
  • 边界只作用于一个文件,所以保留气泡形态;勾选框在气泡关闭时重置。多个文件一律走 D01。
P05

移除仓库登记

L0两处入口合一

移除

从列表移除“alune”?

只移除应用内登记,仓库目录与文件保持不变,之后可以重新打开。

/Users/clean/Desktop/alune

RepositoriesPage.tsx:176 · WorkspaceTree.tsx:363
  • 统一仓库页和工作区树原来的标题、按钮各写各的,现在合并成同一个组件。因为不删文件,用主按钮,不用危险色。
P06

删除连接

L1ConnectionsPage

删除

删除连接“开发机”?

以下仓库将无法访问,远程主机上的文件不受影响。

没有仓库使用此连接,可以放心删除。

moonlight-apiinfradeploy-scripts
ConnectionsPage.tsx:106
  • 改写「与此连接关联的仓库将无法访问」改为列出具体仓库;没有关联时换成一句放心删除的说明。

浮层与菜单

Popover · Dropdown · focus returns to trigger

浮层在打开时把焦点移到面板内的第一个可交互元素,↑↓ 在选项间移动,Esc 关闭并回到触发按钮。点「重播」可以查看出现动画。

L01

切换分支

Popover · 300BranchPicker

main↓2
BranchPicker.tsx:123
  • 可交互在搜索框输入即可筛选;分组全部被过滤后,分组标题也一起隐藏。
  • 无障碍当前分支用勾号和 aria-current 标记,不能再次选择;远程分支用地球图标,和本地分支区分开。
L02

工作区导航

Dropdown · 360RepositorySwitcher

alune · development
RepositorySwitcher.tsx:240
  • 结构底部三个入口从带说明的大按钮压缩成一行,列表区域因此多出两行。
  • 动效从状态栏向上展开,transform-origin 固定在左下角,与触发位置对应。
L03

关联 Worktree

Popover · 360WorktreesPanel

WorktreesMenu.tsx:19
  • 一致行结构和 D04「删除 Worktree」相同;有改动的行提前标出警告色,删除时不会意外。
  • 无障碍删除按钮的无障碍名称带完整路径;已锁定的行禁用删除,并写明锁定原因。
L04

通知

Popover · 340WorkspaceStatusBar

本机执行
WorkspaceStatusBar.tsx:98
  • 结构原来的通知只是纯文本段落,现在复用反馈中心的条目样式:图标、标题、时间,外加一个可选操作。
L05

操作菜单

Dropdown · 240推送 · 标签页

RepositoryToolbar.tsx:309 · RepositoryTabs.tsx:424
  • 规则菜单项以省略号结尾,表示点击后还会打开确认:强制推送会进入 D03,其余项直接执行。
  • 计数批量关闭项在右侧显示将关闭的标签数,为 0 时禁用,不再出现点了没反应的情况。
  • 无障碍危险项除了红色,还配有警告图标;role=menu 支持 ↑↓、Home 和 End 键。

09 Feedback centre

出错时不打断,也不遗漏3 artboards · native <dialog> · tray + inbox

反馈中心汇总整个应用的错误和仓库说明。modal 模式的错误会直接弹出,一次只显示一条,其余排队;notification 和 manual 模式只进入右下角托盘。三种提示用同一套外壳,差别只在色调和第一个按钮。

F01

错误详情

md · 520mode · modalL1

alune仓库配置

无法读取仓库配置

分支、远程和作者信息暂时不可用,其他视图不受影响。

fatal: unable to read config file '/Users/clean/Desktop/alune/.git/config': Permission denied
error: 读取 .git/config 失败(退出码 128)
git config --list exit 128 14:32:08

Esc 关闭并标为已读

1 / 3 · 关闭后显示下一条

Feedback.tsx:204
  • 结构原来的上下文只是一行灰字,现在放进眉题;Git 原始输出用等宽块显示,下方附命令、退出码和时间,复制时一并带上。
  • 队列有多条排队时,页脚写明「1 / 3」,关闭一条后原位显示下一条,不再重复播放入场动画。
  • 无障碍打开时焦点落在「关闭提示」上,与现有实现一致;复制失败时通过 role=status 播报原因。
F02

仓库说明

md · 520mode · manualL0

moonlight-api仓库说明

未配置远程 提交前需要设置作者 浅克隆仅显示已获取的历史

可以继续本地提交。配置远程后可获取、拉取和推送。
user.name 和 user.email 都未配置,Git 会拒绝提交。设置只写入此仓库的 .git/config,不影响全局配置。
只获取了最近 50 个提交,历史、追溯和比较都只覆盖这部分。获取完整历史会下载全部对象,时间取决于仓库大小。
已获取
50 个提交 · depth=50
远程
origin · gitlab.company.cn

这类提示不会自动弹出,只留在托盘里;问题解决后自动移除。

操作前会先关闭此提示

RepositoryContextNotice.tsx:98
  • 色调说明类统一用月光青;只有会阻止下一步的「缺少作者」改用警告色,图标同步切换。
  • 改写「提交前需要设置作者」原来没有正文,现在补一句为什么,并说明设置只写入当前仓库。
F03

托盘与收件箱

Popover · 360popover=manual无遮罩

Feedback.tsx:193 · Feedback.tsx:269
  • 结构收件箱从全屏弹窗改为从托盘向上展开的浮层,不锁定页面;点「查看详情」时才打开 F01。
  • 托盘每个圆点代表一条提示的色调;只要有错误,计数胶囊就变为危险色。完整 Diff 打开时,托盘会重新插入顶层,保持在最上面。
  • 无障碍托盘按钮带 aria-expanded,名称写明条数;色点只作装饰,读屏只读条数。

10 Motion

有分量,但不拖沓enter 360 ms · exit 160 ms · stagger 40 ms

所有动效只改 transform 和 opacity。入场用弹簧曲线,让弹窗有一点重量;退出用加速曲线,比入场快一倍以上。左侧是真实的弹窗层,可以按 4 倍慢放查看,也可以直接点弹窗里的按钮。

M01

弹窗编排

scrim → shell → head · body · foot可交互

alune基于 main

新建分支

↵ 创建

M02

时间轴

0 – 1200 ms同一次打开与关闭

遮罩
240 · spring
光晕
900 · glide
外壳
360 · spring
标题
+40
正文
+80
页脚
+120
取消
160 · exit
确认
260 · 上移
  • 入场外壳 translateY(12px) scale(.97) → 原位,360 ms --ease-spring;头、正文、页脚依次错开 40 ms。
  • 退出160 ms --ease-exit,内容不再错开,避免关闭时拖尾。
  • 确认主按钮先按压 scale(.98),弹窗再上移 8 px 淡出;方向和取消的下沉相反,一眼能分辨结果。
  • 浮层200 ms --ease-glide,从 0.96 缩放,transform-origin 对准触发按钮,不加遮罩。
  • 减弱动效开启 prefers-reduced-motion 时取消位移和缩放,只保留 120 ms 淡入淡出。
  • 模糊只有固定定位的遮罩层使用 backdrop-filter,滚动内容和弹窗本体都不加。

11 Responsive

窄窗口下,变成底部面板≤ 560 px · bottom sheet · 44 px targets

Electron 窗口可以拖得很窄,通过浏览器远程访问时也可能在手机上打开。宽度不足 560 px 时,弹窗贴底变成面板:按钮纵向排列、高 44 px,取消固定在最下方;键盘提示隐藏,底部留出安全区。

工作区alune · main

放弃所有更改?

9恢复
3删除
5保留
L2 · 放弃所有更改

alune基于 main

新建分支

创建后立即切换到新分支。

L0 · 新建分支

分支本地

删除“feature/login”?

已合并到 main,删除后可从 main 的历史中找回提交。

气泡确认 → 面板
≥ 880 px

桌面:按 400 / 520 / 680 / 880 四档宽度居中,距窗口边缘至少 32 px;气泡确认和浮层都锚定在触发按钮旁。

561 – 879 px

紧凑:lg、xl 降为 min(100vw − 32px),正文区域内部滚动;页脚提示保留,按钮仍横排。

≤ 560 px

面板:贴底,只保留上方圆角;按钮纵排、44 px 高,隐藏键盘提示,留出 safe-area-inset-bottom。气泡确认也改为面板。

12 Implementation

从设计稿,到代码antd 6.3.7 · ConfigProvider · modalRender

不引入新的组件库。沿用 antd 6 的语义化 classNames / styles,把双层外壳、语气色和动效令牌集中到一个 AluneModal 和一份全局样式里。下文的键名已按仓库中 antd 6.3.7 的类型定义核对。

组件映射

mapping · 6 primitives

设计稿中的每种容器都对应一个现有的 antd 组件或原生元素,调用方基本不用改。

设计组件实现挂载点语义键 / 钩子默认语气
弹窗 .dlg19 个文件使用 ModalModal → AluneModalConfigProvider.modal + modalRenderroot mask container header title body footer月光蓝
确认弹窗modal.confirm · App.useAppuseAluneConfirm包装 modal.confirmmodalRender · focusable.autoFocusButton按等级
危险确认 L2放弃更改 · 强制推送 · 删除工作树AluneModal level={2}data-level · data-toneokButtonProps.danger · 勾选或输入名称解锁危险
气泡确认分支 · 储藏 · 连接 · 仓库PopconfirmConfigProvider.popconfirmroot container title content · arrow: false柔和红
浮层与菜单分支选择 · 仓库切换 · 工作树 · 通知Popover · DropdownConfigProvider.popover / dropdownroot container · item itemIcon月光蓝
反馈中心Feedback.tsx:193 · :204原生 <dialog> · popover="manual".feedback-dialog · .feedback-tray::backdrop · :popover-open随提示类型

主题接入

ConfigProvider · AluneModal

全局只改外观;需要分级、语气和解锁逻辑的地方换成 AluneModal / useAluneConfirm。

C01

App.tsx

ConfigProvider全局默认

// 只改外观,不改调用方
<ConfigProvider
  theme={config} // 替换 ConfigProvider 中的 fontFamily → Geist
  modal={{
    centered: true,
    mask: { blur: true },
    closeIcon: <X weight="light" />,
    classNames: {
      root: 'a-dlg', mask: 'a-dlg-scrim',
      container: 'a-dlg-core',
      header: 'a-dlg-head', body: 'a-dlg-body',
      footer: 'a-dlg-foot',
    },
  }}
  popconfirm={{ arrow: false, classNames: { root: 'a-pop' } }}
  popover={{ arrow: false, classNames: { root: 'a-pop' } }}
  dropdown={{ classNames: { root: 'a-menu' } }}
>
C02

AluneModal.tsx

modalRender双层外壳

export function AluneModal({
  tone = 'info', level = 0, ...props
}: AluneModalProps) {
  return (
    <Modal
      {...props}
      transitionName="alune-dlg"
      okButtonProps={{
        danger: tone === 'danger',
        ...props.okButtonProps,
      }}
      modalRender={(node) => (
        <div className="a-dlg-shell"
             data-tone={tone} data-level={level}>
          {node}
        </div>
      )}
    />
  );
}

// L2:不自动聚焦主按钮,由勾选或输入名称解锁
modal.confirm({
  ...options,
  focusable: { autoFocusButton: null },
});
C03

styles/dialogs.css

外壳动效原生 dialog面板

.a-dlg-shell {
  padding: 6px;
  border-radius: 26px;
  background: var(--dlg-shell-bg);
  box-shadow: inset 0 0 0 1px var(--dlg-shell-ring), var(--dlg-shadow);
}
.a-dlg-core { border-radius: 20px; background: var(--dlg-core-bg); }
.a-dlg-scrim { background: var(--scrim); } /* 固定层,模糊只作用于这里 */

/* rc-motion:prepare → start → active */
.alune-dlg-enter-start, .alune-dlg-appear-start { opacity: 0; transform: translateY(12px) scale(.97); }
.alune-dlg-enter-active, .alune-dlg-appear-active {
  transition: transform 360ms var(--ease-spring), opacity 240ms var(--ease-enter);
}
.alune-dlg-leave-active {
  opacity: 0; transform: scale(.98);
  transition: transform 160ms var(--ease-exit), opacity 160ms var(--ease-exit);
}

dialog.feedback-dialog::backdrop { background: var(--scrim); backdrop-filter: blur(6px); }

@media (max-width: 560px) {
  .a-dlg .ant-modal { top: auto; max-width: 100vw; margin: 0; padding-bottom: env(safe-area-inset-bottom); }
  .a-dlg-shell { border-radius: 26px 26px 0 0; }
  .a-dlg-foot { flex-direction: column-reverse; }
}
@media (prefers-reduced-motion: reduce) {
  [class*='alune-dlg-'] { transform: none !important; }
}
R

迁移步骤

rollout · 5 steps

  1. 01
    令牌

    把 --ease-*、--t-*、--tone-* 加入全局令牌;替换 ConfigProvider 中的 fontFamily,用 @fontsource-variable/geist 本地打包字体。

  2. 02
    外壳

    接入 ConfigProvider.modal,新增 AluneModal 与 useAluneConfirm,逐个替换 19 处 Modal 和确认调用。

  3. 03
    分级

    按第 04 节的矩阵标注 L0 / L1 / L2。L2 增加确认勾选或输入名称,取消默认聚焦主按钮。

  4. 04
    轻量层

    Popconfirm、Popover、Dropdown 统一使用 a-pop 皮肤并去掉箭头,补齐预检信息:是否已合并、工作区是否干净、被哪些仓库引用。

  5. 05
    反馈与适配

    原生 <dialog> 套用同一套类名和 ::backdrop;加入 ≤ 560 px 面板样式;Playwright 覆盖 Esc、焦点恢复和减弱动效。

QA

验收清单

10 checks

  • 标题和主按钮用动词,不用「确定」
  • L2 不自动聚焦主按钮,需要勾选或输入确认
  • Esc 关闭,焦点回到触发元素
  • Tab 焦点限制在弹窗内
  • 只对 transform 和 opacity 做动画
  • 减弱动效时只保留淡入淡出
  • backdrop-filter 只用于固定遮罩
  • 深浅主题下语气色对比度 ≥ 4.5 : 1
  • ≤ 560 px 为底部面板,按钮高 44 px
  • 月光蓝 #6e86e8 → #8fe3ee 保持不变