feat: increase instance list row height
This commit is contained in:
+1
-1
@@ -58,7 +58,7 @@
|
||||
| T-405 | 设置浏览器 executable 选择与可感知搜索反馈 | T-205,T-206,T-207 | DONE |
|
||||
| T-406 | 实例删除确认中的 User Data Dir 显式删除 | T-301,T-303,T-305 | DONE |
|
||||
| T-407 | 便携默认目录、实例名称唯一与启动目录预检 | T-403,T-404,T-405 | DONE |
|
||||
| T-408 | 实例列表数据行 200% 高度与可访问密度 | T-208,T-306,T-310 | DOING |
|
||||
| T-408 | 实例列表数据行 200% 高度与可访问密度 | T-208,T-306,T-310 | DONE |
|
||||
|
||||
## Backlog
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
|
||||
## 快照
|
||||
|
||||
- 日期:2026-07-27
|
||||
- 阶段:Phase 4 CDP 只读观察与后台入口(T-301 至 T-310、T-201 至 T-208、T-401、T-403 至 T-407 已完成;T-402 已规划;T-408 进行中)
|
||||
- 代码:已建立 Go module `chub`、`cmd/chub` 入口、结构化文件日志、浏览器 domain/application 合约、Chrome/Edge 参数/发现模块、启动 registry、Windows 身份/占用检查、loopback CDP 端口分配与端点校验、受限 CDP 目标读取、exe 同级 JSON 配置与 legacy 安全迁移、优雅关闭、Job Object、真实 Chrome/Edge smoke、启动恢复、CLI JSON 合约、应用内事件总线、UI 状态测试和 Windows smoke 脚本,T-001 至 T-003、T-101 至 T-104、T-201 至 T-208、T-301 至 T-310、T-401、T-403 至 T-407 已完成
|
||||
- 日期:2026-07-28
|
||||
- 阶段:Phase 4 CDP 只读观察与后台入口(T-301 至 T-310、T-201 至 T-208、T-401、T-403 至 T-408 已完成;T-402 已规划)
|
||||
- 代码:已建立 Go module `chub`、`cmd/chub` 入口、结构化文件日志、浏览器 domain/application 合约、Chrome/Edge 参数/发现模块、启动 registry、Windows 身份/占用检查、loopback CDP 端口分配与端点校验、受限 CDP 目标读取、exe 同级 JSON 配置与 legacy 安全迁移、优雅关闭、Job Object、真实 Chrome/Edge smoke、启动恢复、CLI JSON 合约、应用内事件总线、UI 状态测试和 Windows smoke 脚本,T-001 至 T-003、T-101 至 T-104、T-201 至 T-208、T-301 至 T-310、T-401、T-403 至 T-408 已完成
|
||||
- UI:Gio 双页 Shell 使用左侧“实例/设置”导航;实例页以等宽“新建实例 / 刷新实例状态”命令区开始,窄内容区自动堆叠。列表固定显示实例名称、浏览器类型、用户数据目录、代理名称、调试端口、状态和带边框的操作列;代理列只由稳定 `ProxyID` 解析当前名称(无代理时显示“无代理”),长名称省略且不挤压操作列。端口列在未启动/已退出时显示“建议 {端口}”、启动中显示“准备 {端口}”、托管运行显示“实际 {端口}”、外部关联显示“外部 {端口}”,避免将持久首选端口误作监听事实。新建表单从设置的起始端口推荐最低未冲突端口,并基于设置的默认目录预填 `instance-<安全实例名>-<短 ID>`;portable 默认目录始终跟随实际 exe 同级 `user_data_dirs`,自定义目录只影响后续新建实例。实例名称会在保存前按 Windows 规则校验,并以大小写无关方式保持唯一;改名不会移动已有 profile。编辑停止实例可修改首选端口;启动后台会先创建并验证指定 User Data Dir 可写,再优先该端口并避开其他实例的首选/实际端口,成功回退后才保存新的首选值。操作顺序固定为启动/停止(或重新检测)、编辑、删除,删除与相邻操作保持更大间距以降低误点。主操作在已退出时启动,在 Chub 托管运行或调试不可用时显示停止,在启动/停止中禁用,在外部关联、外部占用或未知占用时仅重新检测;优雅停止通过后台 adapter 验证 registry 的 PID/profile 身份并等待其释放,绝不按进程名关闭或接管外部 Chrome/Edge。刷新通过后台回调只检查已保存实例,以配置快照丢弃编辑或删除后的过期结果;它结合 Chub registry、指定 profile 的外部占用证据和 loopback CDP 端点更新状态,但不扫描、接管或关闭其他 Chrome/Edge。双击、Enter 或编辑图标打开实例编辑弹层,支持名称、浏览器类型、User Data Dir、启动 URL、首选端口、以“名称 · 地址”显示的代理选择和只读实际端口;保存保留未公开启动选项,Escape 对脏表单先请求确认,活跃/外部关联实例锁定身份约束字段。设置页使用完整代理地址选择器、代理名称与地址输入及保存/删除操作;选择会回填名称和地址,保存按稳定 `proxyId` 新增或更新并校验名称/地址唯一性,删除未引用代理前必须确认,仍被实例引用的代理会被拒绝。设置页的默认 User Data Dir 与日志目录显示 portable/custom 来源,可通过原生选择器改为自定义路径或恢复为程序目录默认;日志目录在后台探测成功后写入 `chub.log`,不会回退 cwd。新建、编辑和删除实例会保存到本地配置;四个设置路径有独立异步可取消搜索,切换页面后输入和任务状态保留;设置页 Chrome/Edge 路径已接入后台 Windows 原生 `.exe` 选择器,选择和搜索结果始终显示在对应字段旁;命中已填路径会明确确认而非静默完成。新建实例表单使用 Label、Windows 原生目录选择器和 Chrome/Edge RadioButton;CLI `list/events` 已可用,`start/stop/restart` 等待 BrowserManager adapter
|
||||
- T-307:本应用会话启动的受管 Chrome/Edge 根进程由单实例后台 `Wait` 监控;意外退出按 ID、启动代次和 PID 验证后立即变为“已退出”,清空运行时 PID/端口,并以可关闭、可合并的提示告知用户。提示关闭后焦点回到启动操作;Chub 请求停止、外部实例、过期事件和应用关闭取消监控均不提示。
|
||||
- T-308:设置页以可滚动的外层工作区承载浏览器路径、默认目录、运行行为和代理分区;分区、字段、状态提示和保存操作区使用统一的浅色圆角描边。路径字段在紧凑内容区会将输入与“选择/搜索”操作纵向重排,切换页面后滚动、输入与异步搜索状态仍由持久 Gio widget 保留。
|
||||
@@ -18,8 +18,8 @@
|
||||
- T-405:已完成 Windows `OpenFileDialog` executable picker、Chrome/Edge 后台结果通道与字段级状态反馈。搜索同路径时明确确认当前路径可用;选择取消或错误保留输入,选择成功只更新目标字段。已通过单元测试、竞态检测、vet、无控制台打包,以及真实原生对话框取消/预选 Chrome `.exe` 返回 smoke。
|
||||
- T-406:已完成实例删除确认中的 User Data Dir 显式删除。默认仅删除配置;数据删除必须显式勾选且只对已退出/启动失败实例开放。后台在删除前复核 Chub/external profile 占用,目录删除拒绝不安全路径、卷根、文件、链接和 Windows 重解析点;失败与过期结果均保留配置和目录。
|
||||
- T-407:已完成 portable/custom 默认目录来源持久化、实际 exe 同级 `user_data_dirs`/`logs` 解析、历史内置目录迁移、目录选择和恢复默认操作。新实例默认 profile 为安全化名称加短 ID,名称按 Windows 规则和大小写无关方式校验;启动前后台创建并探测 User Data Dir,失败时不启动浏览器。日志目录在后台验证后写入 `chub.log`,不会退回 cwd。已通过单元测试、竞态检测、vet 与无控制台 GUI 打包。
|
||||
- T-408:进行中。将实例列表数据行提升为标准高度的 200%,保留表头、列权重、虚拟滚动及所有行/键盘操作语义。
|
||||
- blocker:无;当前任务为 T-408,随后恢复 T-402。
|
||||
- T-408:已完成。实例数据行最小高度固定为 60 dp(行内图标自然标准高度 30 dp 的 200%),文字、状态和操作区保持垂直居中;长内容可扩展,表头、列权重、虚拟滚动及行/键盘操作语义不变。已通过 UI 单测、竞态检测、全量测试、vet 与无控制台 GUI 打包。
|
||||
- blocker:无;下一任务为 T-402。
|
||||
|
||||
## 当前目录
|
||||
|
||||
|
||||
+4
-4
@@ -3,7 +3,7 @@ id: T-408
|
||||
title: 实例列表数据行 200% 高度与可访问密度
|
||||
phase: 4
|
||||
deps: [T-208, T-306, T-310]
|
||||
status: DOING
|
||||
status: DONE
|
||||
created: 2026-07-28
|
||||
owner: codex
|
||||
---
|
||||
@@ -28,7 +28,7 @@ owner: codex
|
||||
|
||||
## 执行记录
|
||||
|
||||
- 状态:DOING
|
||||
- 变更:已定义高度、保持虚拟化和输入语义的实现边界;待提交文档后实现。
|
||||
- 验证:待执行。
|
||||
- 状态:DONE
|
||||
- 变更:将行内图标的 30 dp 自然标准高度与 60 dp 数据行最小高度固化为命名常量;每条数据行以 60 dp 最小高度布局,现有文本、状态和操作区继续居中,内容可自然扩展。表头、列权重、行间距、虚拟列表、行/按钮命中优先级和键盘操作未改变。
|
||||
- 验证:`go test ./internal/ui`、`go test ./...`、`go test -race ./internal/ui`、`go vet ./...`、`cmd /c build.bat` 均通过。
|
||||
- 阻塞:无。
|
||||
|
||||
+10
-2
@@ -40,6 +40,10 @@ const (
|
||||
instancePortColumnWeight = 9
|
||||
instanceStatusColumnWeight = 10
|
||||
instanceActionColumnWeight = 18
|
||||
instanceActionIconSize = unit.Dp(16)
|
||||
instanceActionIconInset = unit.Dp(7)
|
||||
instanceStandardRowHeight = instanceActionIconSize + 2*instanceActionIconInset
|
||||
instanceListRowMinHeight = 2 * instanceStandardRowHeight
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -1137,6 +1141,10 @@ func (s *Shell) instanceTable(gtx layout.Context) layout.Dimensions {
|
||||
|
||||
func (s *Shell) instanceListRow(row InstanceRow) layout.Widget {
|
||||
return func(gtx layout.Context) layout.Dimensions {
|
||||
minimumHeight := gtx.Dp(instanceListRowMinHeight)
|
||||
if gtx.Constraints.Min.Y < minimumHeight {
|
||||
gtx.Constraints.Min.Y = minimumHeight
|
||||
}
|
||||
return layout.Background{}.Layout(gtx,
|
||||
func(gtx layout.Context) layout.Dimensions {
|
||||
if s.selectedInstanceID == row.ID {
|
||||
@@ -1305,8 +1313,8 @@ func (s *Shell) instanceIconButton(click *widget.Clickable, icon *widget.Icon, d
|
||||
gtx = gtx.Disabled()
|
||||
}
|
||||
style := material.IconButton(s.theme, click, icon, description)
|
||||
style.Size = unit.Dp(16)
|
||||
style.Inset = layout.UniformInset(unit.Dp(7))
|
||||
style.Size = instanceActionIconSize
|
||||
style.Inset = layout.UniformInset(instanceActionIconInset)
|
||||
style.Background = color.NRGBA{}
|
||||
style.Color = iconColor
|
||||
return style.Layout(gtx)
|
||||
|
||||
@@ -3,6 +3,7 @@ package ui
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"image"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -10,6 +11,8 @@ import (
|
||||
"time"
|
||||
|
||||
"gioui.org/layout"
|
||||
"gioui.org/op"
|
||||
"gioui.org/unit"
|
||||
"gioui.org/widget/material"
|
||||
)
|
||||
|
||||
@@ -83,6 +86,24 @@ func TestInstanceListColumnWeightsPrioritizeDirectoryAndCompactAction(t *testing
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstanceListRowsUseDoubleStandardHeightAtEveryDPIScale(t *testing.T) {
|
||||
if instanceListRowMinHeight != 2*instanceStandardRowHeight {
|
||||
t.Fatalf("row height = %v, want two times standard height %v", instanceListRowMinHeight, instanceStandardRowHeight)
|
||||
}
|
||||
shell := NewShell(material.NewTheme())
|
||||
for _, scale := range []float32{1, 2} {
|
||||
gtx := layout.Context{
|
||||
Ops: new(op.Ops),
|
||||
Constraints: layout.Constraints{Max: image.Pt(1200, 400)},
|
||||
Metric: unit.Metric{PxPerDp: scale, PxPerSp: scale},
|
||||
}
|
||||
dimensions := shell.instanceListRow(shell.rows[0])(gtx)
|
||||
if want := gtx.Dp(instanceListRowMinHeight); dimensions.Size.Y != want {
|
||||
t.Fatalf("scale %.0f row height = %d, want %d", scale, dimensions.Size.Y, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellDeletesOnlyTheRequestedInstance(t *testing.T) {
|
||||
shell := NewShell(material.NewTheme())
|
||||
target := shell.rows[1]
|
||||
|
||||
Reference in New Issue
Block a user