# 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 |