Files
u-tabs/docs/02-技术文档/架构设计.md
绝尘 a027fe1703 新增: u-tabs 初始版本
Go TUI 项目启动器,基于 bubbletea v2 + lipgloss v2。
支持分组 Tab、多选启动、编号跳转、Windows Terminal 集成。
2026-05-16 21:01:03 +08:00

5.3 KiB
Raw Permalink Blame History

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 结构

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

type Workspace struct {
    Index  int    // 全局索引
    N      int    // 编号
    Title  string // 短名
    Prompt string // 描述
    Tech   string // 技术栈
    Deploy string // 部署情况
    Dir    string // 目录路径
    Group  string // 分组标签
}

4.2 Group

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

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宽不会切断多字节字符

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 脚本模板

$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 编码

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