Files
terminalX/docs/design/UI-DESIGN-HANDOFF.md
kid 28e9cfc207 feat: UI 商业化改造 + 连接前 tmux 会话选择(真机验证)
按 Claude Design 定稿(归档在 docs/design/)重做 UI,并把 tmux 从
「从不自动进」修成「连接前探测 → 让用户选会话」。

设计令牌与导航地基
- Theme 拆三层:TXAccent(恒定 blurple) / TXChrome(中性阶×4 表) / TXFlavor(Catppuccin 终端)
- vendor JetBrains Mono 四权重(附 OFL 许可)
- AppRouter + SessionManager:多会话并存,路由与会话生命周期解耦
- SessionCanvas 常驻挂载会话 surface(摘除即丢内容,也是缩回动画的前提)

按设计稿落地的屏
- 沉浸轨道页:56pt 轨道 + 浮起标题/状态胶囊 + 侧边栏三态(遮罩不 resize、Pin 各一次)
- 首页:活动会话卡(readViewportText 文本镜像)+ 主机网格 + 筛选 chips
- 关闭二次确认(tmux 仅断开 / 原生窗口两套文案)、分屏菜单、pane 拖拽条
- 空状态:首次运行 / 无会话 / 搜索无命中 / 连接中·失败·已关闭

tmux 真实链路(真机查出并修掉 4 个 bug)
- 全代码库从来没人发起 attach → 连接前探测 + 会话选择器(接回 / 新建 / 原生终端)
- format 分隔符 tab 经 PTY 变成下划线 → 改用 |:|
- controller 变化不冒泡到 session → Combine 转发(否则数据解析对了 UI 不刷新)
- 当前会话名不能靠 session_attached 反推 → 改用 display-message

传输层:SSH connect 加超时(原来阻塞到系统 TCP 超时 75s+)
无头验证设施:假会话 fixture · terminalx://ui/* 驱动 · 横屏截图脚本 · 对拍走查法
TXCore 45 tests 绿(新增 5 个会话探测单测)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-25 22:59:35 +08:00

242 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Handoff: terminalX — iPadOS 终端 App沉浸轨道方案
## Overview
terminalX 是面向 iPadOS / iOS 的原生终端运维工具,解决 iPad 远程运维 macOS / Linux 的痛点。技术选型:**libghostty**(终端引擎)· **mosh**(抗断线传输)· **Tailscale tsnet**(用户态入网)· **tmux control mode**(会话复用)。平台优先级 **iPadOS > iOS > macOS(Apple Silicon)**
本包描述的是**终端主界面(沉浸轨道)+ 首页(主机管理)+ 设置**这一套已定稿的 UI含侧边栏三态、分屏模型、关闭/最小化语义、竖屏与 iPhone 降级规则。
## About the Design Files
`terminalX iPadOS.dc.html` 是**设计稿**,不是生产代码。它用 HTML/CSS 表达最终视觉与交互意图1:1 像素尺寸),目的是让你在**目标工程里用原生技术重建**——本项目的目标环境是 **SwiftUIiPadOS 26 / iOS 26**,请使用工程已有的架构与组件模式实现,不要移植 HTML/CSS。
打开方式:浏览器直接打开该文件即可,画板可自由缩放平移。每块屏幕都带 `data-screen-label`,便于对照本文档。
## Fidelity
**High-fidelityhifi**。颜色、字号、圆角、间距、行高均为最终值,可按本文档的精确数值实现。文案(简体中文,技术术语保留英文)也是最终稿。
唯一例外:终端内容区的命令输出是**示意数据**,用于验证排版密度与配色,不必照搬。
---
## Design Tokens
### 颜色 — App chromeNocturne 设计系统)
| 用途 | Hex | Nocturne token |
| --- | --- | --- |
| 画布底 / 屏幕底 | `#161826` | `--color-bg` |
| 侧边栏 / 轨道 / 卡片底 | `#1b1d2b` | surface-dark |
| 控件底(输入框、次要按钮、徽标) | `#232532` | `--color-surface` |
| 舞台底pane 之间的缝) | `#11121c` | bg-deep |
| 分隔线(弱) | `#2c2f3d` | neutral-800 |
| 描边(常规) | `#3f424d` | neutral-700 |
| 描边(强调 / 对话框) | `#595d6c``#4a4d5e` | neutral-600 |
| 强调色(唯一) | `#9184d9` | `--color-accent` |
| 强调 - 边框 | `#796cbf` | accent-600 |
| 强调 - 选中底 | `#2b2741` | accent-900 |
| 强调 - 选中描边 | `#423a6a` | accent-800 |
| 强调 - 文字 | `#b5abfc` / `#d2cefd` | accent-400 / -300 |
| 强调 - 弱化文字 | `#5d5294``#968ae0` | accent-700 / -500 |
| 主文字 | `#e9e9ed` | `--color-text` |
| 次文字 | `#cfd3e5` / `#b2b6ca` | neutral-300 / -400 |
| 三级文字 | `#9397ab` | neutral-500 |
| 弱文字 / 占位 | `#75798c` | neutral-600 |
| 极弱(提示、注脚) | `#4a4d5e` | neutral-700 |
**规则**:强调色只做线、点、选中底和文字,**绝不大面积铺色**;不使用纯黑纯白。
### 颜色 — 终端内容区Catppuccin Mochalibghostty 渲染)
`base #1e1e2e`pane 底)· `mantle #181825` · `surface0 #313244`vim 状态栏)· `text #cdd6f4` · `subtext #a6adc8` · `overlay #6c7086`(次要输出)· `surface2 #585b70`(禁用/缓存态)
语义色:`green #a6e3a1`(成功、提示符)· `red #f38ba8`error· `yellow #f9e2af`warning、git 标记)· `blue #89b4fa`(路径、字段)· `mauve #cba6f7`(关键字)· `teal #94e2d5`(勾选)· `peach #fab387`(重连中)· `pink #f5c2e7`
设置页可切换 Mocha / Macchiato / Frappé / Latte**chrome 的中性阶随之推导,强调色恒为 Nocturne blurple `#9184d9`**。
macOS 窗口按钮沿用系统色:关闭 `#ff5f57`,最小化 `#febc2e`13pt 圆点,`inset 0 0 0 .5px rgba(0,0,0,.25)`,仅悬停/触摸时显出符号)。
### 字体
全局 **JetBrains Mono**400 / 500 / 600 / 700chrome 与终端同源。中文走系统回退PingFang SC
| 场景 | 规格 |
| --- | --- |
| 屏幕主标题(首页 terminalX | 500 34px, letter-spacing -.025em |
| 页面标题(设置、总览) | 500 2428px, ls -.02em |
| 分区标题 | 500 22px, ls -.02em |
| 分组小标题(活动会话 / TMUX | 600 10px, ls .09em, uppercase, `#75798c` |
| 列表主行 | 400500 13px |
| 列表副行 / 元信息 | 400 10.511px, `#75798c` |
| 徽标 / 胶囊 | 500 11.512px |
| 终端正文(主 pane | 400 13.5px / 1.55 |
| 终端正文(次 pane / 缩略) | 400 1112px / 1.5 |
| 终端 pane 头 | 500 11px, `#6c7086` |
| 底部提示条 | 400 11.5px, `#75798c` |
### 间距 / 圆角 / 阴影
- 间距2 / 6 / 8 / 9 / 10 / 12 / 14 / 16 / 18 / 20 / 26 / 34 / 40px
- 圆角:徽标 57 · 控件 89 · 卡片 1113 · 对话框 1415 · 屏幕卡 18 · 手机卡 38
- 阴影:浮起胶囊 `0 10px 30px rgba(0,0,0,.55)`;侧边栏浮层 `24px 0 60px rgba(0,0,0,.55)`;对话框 `0 30px 80px rgba(0,0,0,.7)`;卡片 `0 6px 18px rgba(0,0,0,.4)`
- 遮罩:`rgba(10,11,18,.42.5)`,浮层用 `backdrop-filter: blur(20px)`
### 图标
**Phosphor Icons**regular。用到`magnifying-glass` `house` `plus` `gear-six` `caret-double-left` `caret-double-right` `caret-down` `caret-left` `push-pin` `columns` `terminal-window` `hard-drives` `desktop-tower` `cpu` `key` `shield-check` `lightning` `paint-brush` `text-aa` `keyboard` `export` `info` `warning` `x` `minus` `arrow-right` `arrow-left` `lightbulb` `moon` `check-circle` `wifi-high` `battery-high` `browsers` `rows` `git-branch` `plugs` `trash` `arrows-in-simple` `textbox` `sidebar-simple` `globe-hemisphere-west`
SwiftUI 实现请换成等义 **SF Symbols**(例如 `house` / `magnifying-glass``house``magnifyingglass``caret-double-left``chevron.compact.left``sidebar.left``push-pin``pin`)。
---
## Layout 基准
- 画板尺寸iPad Pro 13″ 横屏 **1366×1024pt**;竖屏 **1024×1366pt**iPhone **393×852pt**
- 折叠轨道宽 **56pt**(竖屏 48pt元素中心线统一在 28pt竖屏 24pt
- 展开侧边栏宽 **288pt**
- pane 之间的缝 **2pt**(拖拽热区 **12pt**,把手 44×3pt 圆角 2pt
- 触控命中区不低于 **44pt**(键盘附加行按键高 3840pt + 6pt 间距 = 44pt 节距)
- 终端主 pane 顶部预留 **86pt** 给浮起胶囊2c 因多一条横幅为 122pt预留带用 padding-top 实现,内容容器 `min-height:0; overflow:hidden` 防止向上溢出
- Home indicator 条200×4ptiPhone 140×5pt
---
## Screens / Views
### 1) 终端主界面 · 沉浸轨道 — `1b 终端-沉浸轨道`
**Purpose**:真正干活的界面,终端面积最大化。
**Layout**`HStack`:左 56pt 轨道 + 右舞台。舞台是 `VStack`pane 区flex+ 硬件键盘提示条 34pt + home 区 14pt。pane 区 padding 2pt上下两个 pane 比例 1.7 : 1中间 12pt 拖拽条。
**轨道自上而下**Logo 方块34×34圆角 11`#232532`inset 描边 `#3f424d`,字形 `` 700 16px `#b5abfc`**不可点**)→ 搜索图标 → 首页图标 → 1pt 分隔 → 主机头像38×38 圆角 11两字缩写选中底 `#2b2741` + inset 描边 `#5d5294`;右上角 8pt 状态点,`box-shadow: 0 0 0 2px #1b1d2b` 挖底)→ 虚线 + 号 → 分隔 → `TMUX` 标签600 9px ls .1em+ window 数字34×30选中底 `#232532` + inset 描边 `#4a4d5e`)→ `Spacer` → 连接状态点 8pt → 展开键(`caret-double-right`34×34 底 `#232532` 描边 `#3f424d`)→ 设置齿轮。
**浮起 chrome**`position: absolute``backdrop-filter: blur(20px)`,底 `rgba(27,29,43,.86.9)`,描边 `#3f424d`,圆角 11
- 左上标题胶囊 `top 14, left 16`:红/黄圆点 → 1pt 竖分隔 → 6pt 绿点 → 主机名500 12.5px)→ 「当前 window title · cwd」400 11.5px `#75798c`
- 右上状态胶囊 `top 14, right 16`:三格分层徽标 `tsnet` | `mosh 41ms` | `tmux dev`,格间 1×16pt 竖线
**底部提示条**:接硬件键盘时只留 34pt 提示条(快捷键清单);触控时替换为键盘附加行(两行,见 iPhone / 竖屏)。
### 2) 侧边栏三态 — `3a 侧边栏-遮罩态`、`3a 侧边栏-Pin 态`
同一条栏的三个状态,**元素不增不减**,只是展示密度不同:
| 状态 | 宽度 | 行为 |
| --- | --- | --- |
| 折叠 | 56pt | 只显图标与两字缩写 |
| 遮罩(默认展开) | 288pt | 面板**盖住**轨道(`left: 0`,不并排,避免两套入口),右侧终端压 `rgba(10,11,18,.5)` 遮罩,**点终端任意处收起**;终端不 reflow不触发 PTY resize |
| Pin | 288pt | 点面板右上 `push-pin` 后钉住:遮罩消失,终端变窄,**只在钉住/解除时各 resize 一次**;再点解除回遮罩态 |
**开合语义(务必唯一、不歧义)**`` 方块是 App logo不可点。开合只有一枚按钮两态方向相反——折叠态在轨道底部 `caret-double-right`(展开),展开态在标题右侧 `caret-double-left`收起。Pin 是独立按钮,只在展开态出现。快捷键 `⌘\` 开合。
**面板内容自上而下**Logo + `terminalX`500 14px+ Pin + 收起 → 搜索框32pt 高,圆角 8→ **首页**行(`house` + 「首页」+ `⌘⇧H`)→ 分组「活动会话 n」→ 会话行 → 1pt 渐隐分隔 → 分组「TMUX · <主机> · <会话>」→ window 行 + 「新建 window ⌘T」→ `Spacer` → Tailscale 状态脚(绿点 + tailnet + 设备 IP + 齿轮)。
**行规格(左缘必须对齐)**:会话行 = 26pt 头像列 + 10pt gapwindow 行 = 26×22pt 徽标列 + 10pt gap两者标题左缘同为 **x=53**。选中态用 `box-shadow: inset 0 0 0 1px #423a6a` 而非 `border`,避免选中时内容位移 1pt。
### 3) 首页 · 主机管理(落地页)— `1e 主机-会话卡`
**Purpose**App 冷启动落点;「回到刚才那个会话」是首要动作。
**Layout**:状态栏 24pt → 页头terminalX 500 34px + 副行)+ 右侧搜索 220pt / 新建连接 / 设置 → 分组「活动会话」→ **3 列会话缩略卡flex:1** → 分组「全部主机」+ 筛选 chips → **4 列主机卡(按内容高度)** → home 区。左右留白 40pt。
**会话缩略卡**(圆角 13选中卡描边 `#423a6a` + `0 6px 18px rgba(0,0,0,.4)`):卡头 36pt状态点 + 主机名 + tmux 会话 + RTT→ 终端缩略(`#1e1e2e`11px/1.5**内容底对齐**,真实回滚)→ 卡脚 38pt「n windows · 上次活跃」+ `⌘1/2/3` + 主动作「回到会话 →」)。离线卡加 `rgba(24,26,40,.72)` + `blur(1.5px)` 遮罩,内含 26pt 转圈 + 两行说明;**遮罩必须是 pane 的最后一个子节点**pane 为 `position: relative`)。
**最小化回来的卡**:置顶并标「最小化中」,转场是从终端缩回该卡的动画。
### 4) 新建分屏 — `3b 新建分屏菜单`
**不问方向**(竖分 `⌘D` / 横分 `⌘⇧D` 本来就是两个入口)。菜单只问「分到哪里」:
- 分组「tmux 会话」:当前会话(带「当前」标签)、其余 detached 会话title · n windows · 上次活跃)、「新建 tmux 会话…」
- 分隔线
- 「原生窗口 / 不经 tmux · 关 App 即结束」
选完**立即分屏**。菜单 360pt 宽,圆角 14`#1b1d2b` + 描边 `#595d6c`;行高 ~40pt前导列统一 20pt状态点也要包在 20pt 居中列里,保证五行文字左缘一致 x=50
**模型约束**:分屏只发生在**同一台主机内**。卡片内部的分割 = 真 tmux pane`split-window`,服务端持有,断线原样恢复);跨主机分屏**已取消**(层级过深),要同时看两台机器就切会话或 Pin 侧边栏。
### 5) 关闭 / 最小化 — `3c 关闭确认`
标题胶囊左侧 macOS 红黄圆点:
- **黄点(最小化)****不确认**,直接回首页,会话继续跑;卡片置顶标「最小化中」。手势:终端下滑。快捷键 `⌘M`
- **红点(关闭)****弹一次确认**,按主机能力分两套文案:
- tmux 会话 → 主按钮「仅断开 ⏎」(`detach`,服务端继续跑);左侧次要文字按钮「结束会话」(`kill-session`,需再确认)
- 原生窗口 → 琥珀警告「没有 tmux 兜底,前台进程会随之结束」;推荐「取消」,危险按钮「关闭窗口」(描边 `#8d5560`,底 `rgba(92,58,63,.35)`,字 `#f5a0ac`
- 快捷键 `⌘W``esc` 取消;`⏎` 选推荐项
对话框 436pt / 404pt 宽,圆角 15。
### 6) 首页 ⇄ 终端 跳转关系 — `3d 页面跳转关系`
**进入终端2 条)**:点活动会话卡 / 会话行 → 恢复到**上一个焦点 pane**;新建连接或主机 + → 建好即进终端,侧边栏同步出现该会话。
**离开终端3 条)**:侧边栏首页行(`⌘⇧H`,会话完全不动)· 黄点最小化(同上但语义是"待会儿还回来")· 红点关闭二次确认tmux detach / 原生窗口结束)。
换会话不必回首页——侧边栏直接切;回首页表示"换任务"。会话总览层(原 `⌘O` 缩放)**已取消**。
### 7) 无 tmux 主机 — `2c 无 tmux 主机`
轨道下半段的 `TMUX` 段换成「窗口」段(客户端窗口,每个 = 一条独立 SSH channel / mosh 会话,可随意新建)。状态胶囊第三格写「无 tmux · 客户端窗口」而非报错。终端顶部一条可关闭的行内横幅:说明窗口由 App 维持、mosh 兜底,但**关掉 App 不会在服务端继续跑**,右侧给「安装并启用 tmux」次要按钮。不弹窗、不强推。
### 8) 竖屏与 iPhone — `2d iPad 竖屏`、`2d iPhone 竖屏`、`1h iPhone-终端`、`1h iPhone-主机`
- **iPad 竖屏1024×1366**:轨道降到 48pt元素中心 24pt同样含搜索/首页/展开键pane 由左右平铺改为**上下堆叠,最多两层**,多余 pane 折成顶部段控;状态胶囊压缩为「主机名 + RTT」两枚底部常驻单行键盘附加行。
- **iPhone393×852**:轨道下沉为**底部主机条**ms / u2 / nh 胶囊);单 pane 全屏,顶部 3 段进度式 pane 指示 +「左右轻扫切 pane」下拉标题打开会话列表键盘附加行两行esc/ctrl/tab/~///| 与 ↑↓←→/⌘K主机页用底部 tab主机 / 会话 / 密钥 / 设置)。
### 9) 设置 · 外观 — `1g 设置-外观`
左 280pt 类别栏(外观与主题 / 终端与字体 / Tailscale / 密钥与安全 / mosh 与重连 / tmux 集成 / 键盘与手势 / 诊断日志,脚注版本行),右内容区 padding 28/40。内容标题 + 说明 → 4 张 Catppuccin 主题卡88pt 预览 + 5 色点 + 名称,选中描边 `#796cbf` + `check-circle`)→ 两栏(左「实时预览」终端框,右「排版」面板:字体、字号 13.5pt、行高 1.50、连字、CJK 等宽补偿、跟随系统深浅色)。
---
## Interactions & Behavior
| 交互 | 行为 |
| --- | --- |
| 侧边栏开合 | `⌘\` 或开合键遮罩态点终端收起Pin 切换推挤/浮层 |
| 切会话 | 轨道主机头像 / 侧边栏会话行 / `⌘1…9`;恢复到该会话上一个焦点 pane |
| 切 window | 轨道数字 / 侧边栏 window 行 / `⌃⇥` |
| 分屏 | `⌘D` 竖分、`⌘⇧D` 横分 → 弹会话选择菜单 → 立即分屏 |
| 新建 | `⌘T` 新 tmux window`⌘⌥T` 原生窗口 |
| 调整 pane 比例 | 拖 12pt 分隔条(把手 44×3pt松手才向 tmux 发 `resize-pane` |
| 回首页 | `⌘⇧H` / 侧边栏首页行 |
| 最小化 | 黄点 / `⌘M` / 终端下滑;无确认 |
| 关闭 | 红点 / `⌘W`;二次确认,`⏎` = 推荐项,`esc` 取消 |
| 命令面板 | `⌘K`(模糊匹配主机、会话、动作) |
| 硬件键盘 | 检测到即收起附加行,只留 34pt 提示条;拔掉恢复附加行 |
| 长按 `⌘` | 显示全部快捷键面板iPadOS 系统行为) |
**动效**:状态切换 200260ms `easeOut`;侧边栏浮层从左滑入 + 遮罩淡入;最小化 = 终端缩回首页卡片matched geometry重连转圈 1s 线性循环。
**状态徽标规则(分层,只染出问题的那层)**
1. `tsnet` — 网络可达;断则本格转灰/琥珀
2. `mosh <RTT>` — 数字实时跳动;`≤120ms` 绿 `#a6e3a1``120200ms` 中性,`>200ms` 琥珀 `#fab387`
3. `tmux <会话名>` / 「无 tmux · 客户端窗口」
**重连**:指数退避,上限 30s卡片/胶囊显示「重连中 · 第 n 次 · ms 后」;屏幕内容本地缓存,恢复后原样接回(目标 <3s)。
**安全**host key TOFU 固定在钥匙串不匹配时**拦截连接**并告警私钥存 Secure Enclave不可导出密码回退默认关闭
## State Management
```
AppRoute = .home | .terminal(sessionID)
SidebarState = .collapsed | .overlay | .pinned
Session { id, host, kind: .tmux(name) | .nativeWindow, windows[], focusedPaneID,
transport: .ssh | .sshOverTsnet | .mosh | .moshOverTsnet,
link: .direct(rtt) | .relay(derp, rtt) | .reconnecting(attempt, nextIn) | .offline,
lastActiveAt, isMinimized }
TmuxWindow { index, title, cwd, panes[] } // title 为 shell 默认值时 UI 回退显示 cwd
Host { id, name, group, user, addr, port, os, tsnetIP, hasTmux, hostKeyPinnedAt }
```
关键转换`选择会话 → .terminal``首页/最小化 → .home`会话保留`关闭 → detach 或 kill → .home``Pin → .pinned`触发一次 PTY resize)。
## Files
- `terminalX iPadOS.dc.html` 全部 13 块屏幕的设计稿画板可缩放平移
- `CLAUDE.md` / `README.md`源项目文档若已在仓库中则以仓库版本为准
## Assets
无位图资产图标全部来自 Phosphor实现时换 SF Symbols字体 JetBrains MonoGoogle Fonts终端配色 Catppuccin