新增: u-tabs 初始版本

Go TUI 项目启动器,基于 bubbletea v2 + lipgloss v2。
支持分组 Tab、多选启动、编号跳转、Windows Terminal 集成。
This commit is contained in:
2026-05-16 21:01:03 +08:00
commit a027fe1703
12 changed files with 1356 additions and 0 deletions

View File

@@ -0,0 +1,222 @@
# u-tabs 架构设计
> 最后更新2026-05-15 | 基于 u-tabs v0.3.0
## 1. 项目概述
u-tabs 是一个 TUI 工作空间启动器,用 Bubble Tea v2 实现。管理多个工作空间YAML 配置驱动),通过 Windows Terminal + Claude Code 标签页启动。
**技术栈**: Go 1.26 / BubbleTea v2 / LipGloss v2
## 2. 架构
```
main.go → Model (app.go)
├── 分组 Tab 栏
├── 左侧项目列表
├── 右侧详情面板
└── Enter → wt.exe → claude
```
## 3. 配置系统
### 3.1 加载优先级
```
~/.u-tabs/config.yaml → exe同目录/config.yaml → 内置默认值 (workspace.go)
```
### 3.2 YAML 结构
```yaml
groups:
- label: CORE # 分组标签,决定编号基数
desc: 核心业务
items:
- title: flux # 短名
prompt: 描述 # AI prompt
tech: 技术栈 # 精确到版本
deploy: 部署 # 服务器/端口/域名
dir: 目录路径 # 启动目录
```
### 3.3 编号规则
`GroupConfig.Base` 字段决定,若未设置默认从 0 开始。
### 3.4 数据转换
`Config.ToInternal()` 将 YAML 配置转为运行时结构,自动生成:
- `Index`: 全局索引 (0, 1, 2...)
- `N`: 编号 (base + item索引)
- `Group`: 继承父级 label
- `Dir`: `~` 展开为用户主目录
## 4. 数据模型
### 4.1 Workspace
```go
type Workspace struct {
Index int // 全局索引
N int // 编号
Title string // 短名
Prompt string // 描述
Tech string // 技术栈
Deploy string // 部署情况
Dir string // 目录路径
Group string // 分组标签
}
```
### 4.2 Group
```go
type Group struct {
Label string
Desc string
}
```
### 4.3 运行时索引
| 变量 | 类型 | 用途 |
|------|------|------|
| `Groups` | `[]Group` | 分组定义 |
| `AllWorkspaces` | `[]Workspace` | 全部工作空间 |
| `wsByNum` | `map[int]*Workspace` | 编号→Workspace O(1) 索引 |
## 5. 主模型 (app.go)
### 5.1 Model
```go
type Model struct {
activeGroup int // 当前分组索引
cursor int // 分组内光标
selected map[int]bool // 多选标记
inputBuf string // 数字快捷输入缓冲
width, height int
launched string // 启动提示
}
```
### 5.2 Update 消息路由
```
KeyPressMsg
├── q, ctrl+c → tea.Quit
├── tab, right, l → activeGroup++ → cursor=0
├── shift+tab, left, h → activeGroup--
├── 1, 2, 3, 4 → 跳转分组
├── up, k → cursor--
├── down, j → cursor++
├── Space → toggleMultiSelect
├── enter → inputBuf非空? launchByInput() : launchSelected()
├── c → copyCommand()
└── [0-9] → 追加到 inputBuf
```
### 5.3 View 渲染
```
View()
└── 启动器模式:
├─ 第1行: "u-tabs"标题 + 分组 Tab 栏
├─ 分隔线
├─ 左右布局 (JoinHorizontal):
│ ├── 左侧列表: 编号 + 标题 + 描述 (CJK安全截断)
│ └── 右侧详情: 目录/编号/描述/技术/部署/命令预览
├─ 输入缓冲提示
├─ 启动成功提示
└─ 帮助栏 (快捷键高亮)
```
### 5.4 CJK 安全截断
`truncateByWidth()` 按 rune 显示宽度截断,中文=2宽英文=1宽不会切断多字节字符
```go
func truncateByWidth(s string, maxW int) string {
w := 0
for i := 0; i < len(s); {
_, size := utf8.DecodeRuneInString(s[i:])
rw := runeWidth(rune(s[i]))
if w+rw > maxW { return s[:i] }
w += rw
i += size
}
return s
}
```
## 6. 启动逻辑
### 6.1 流程
```
用户按 Enter
buildLaunchScript(ws) → PS 脚本
encodePSCommand(script) → UTF-16LE → Base64
exec.Command("wt.exe", "-w", "0",
"-d", ws.Dir,
"--tabColor", randomColor,
"pwsh", "-NoExit", "-EncodedCommand", encoded).Start()
Windows Terminal 新标签页 → claude 交互模式
```
### 6.2 PS 脚本模板
```powershell
$Host.UI.RawUI.WindowTitle = "{Title}"
Write-Host "=== {Title} ===" -ForegroundColor Cyan
Write-Host "Prompt: {Prompt}" -ForegroundColor Yellow
cd "{Dir}"
claude --name "{Title}" --permission-mode bypassPermissions
```
### 6.3 UTF-16LE 编码
```go
func encodePSCommand(script string) string {
u16 := utf16.Encode([]rune(script))
b := make([]byte, len(u16)*2)
for i, r := range u16 {
b[i*2] = byte(r)
b[i*2+1] = byte(r >> 8)
}
return base64.StdEncoding.EncodeToString(b)
}
```
## 7. 样式系统 (Tokyo Night 主题)
### 7.1 色板
| 常量 | Hex | 用途 |
|------|-----|------|
| BgDark | #1a1b26 | 深底色 |
| BgPanel | #292e42 | 面板/边框/分隔线 |
| Dim | #565f89 | 次要文字 |
| Fg | #a9b1d6 | 正文 |
| Bright | #c0caf5 | 高亮文字 |
| Accent | #7aa2f7 | 主强调 (蓝色) |
| Success | #9ece6a | 绿色 (部署/标记) |
| Warning | #e0af68 | 黄色 (输入提示) |
| Cyan | #7dcfff | 青色 (编号/详情) |
| Purple | #bb9af7 | 紫色 (选中行) |
| Red | #f7768e | 红色 (CORE分组) |
### 7.2 分组颜色
| 分组 | Hex |
|------|-----|
| CORE | #f7768e |
| LAB | #9ece6a |
| TOOLS | #e0af68 |
| ME | #bb9af7 |
| TEMP | #7dcfff |