fix(frontend): refine settings dialog layout
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
# 印度占星 Skill 更新日志
|
||||
|
||||
## 2026-09-19 — 设置弹窗布局与资料入口整理
|
||||
|
||||
设置弹窗的四个分区继续共用固定外框,桌面导航加宽并与内容区分隔,右侧内容增加稳定内边距,表单阅读宽度更舒适;导航去掉会误导成页面跳转的右箭头。个人资料的账户头像预览放大到 56px,星盘资料的“添加其他人”移到分组标题旁,列表较长时也容易找到。Skill 版本不变(纯界面改动)。
|
||||
|
||||
## 2026-09-18 — 账户弹窗重新能点;校正读不到下一题时会说出来
|
||||
|
||||
侧栏打开个人资料、星盘资料、设置、点数或退出登录时,弹窗里的按钮和表单又能点了,确认退出也恢复了。生时校正如果没读到下一题,会写「这一问还没读到。」并给出「重新读取」,不再停在空白里装没事。Skill 版本不变。
|
||||
|
||||
@@ -12781,3 +12781,19 @@
|
||||
- 关联记录:BUG-525(题干经 asked_turn_id 挂上)、BUG-930(回答钉顶:只见最后一轮是设计)、BUG-965
|
||||
- 复发自:待确认
|
||||
- 修复版本:`824ecff0`
|
||||
|
||||
## BUG-970 | 设置弹窗内容区留白过大、导航入口语义像页面跳转
|
||||
|
||||
- 状态:investigating
|
||||
- 首次发现:2026-09-19
|
||||
- 最近更新:2026-09-19
|
||||
- 影响面:设置弹窗的桌面布局、个人资料头像区、星盘资料列表入口。
|
||||
- 用户现象:设置弹窗左侧导航过窄,右侧内容贴边且留下较大空白;分区按钮尾部箭头暗示会进入下一页;个人资料头像锚点偏小;星盘资料的添加入口位于列表末尾,资料较多时不稳定。
|
||||
- 触发条件:登录后打开设置弹窗,在桌面宽度切换四个设置分区,查看个人资料或星盘资料列表。
|
||||
- 根因:旧布局使用 176px 导航、三列导航按钮(为同弹窗内切换保留无实际用途的箭头列)、内容区没有明确内边距,表单子内容上限为 440px;头像编辑仍以 48px 预览布局;添加入口与列表内容耦合在列表底部。
|
||||
- 修复:导航调整为 200px 并增加 hairline 边界,移除误导性尾部箭头;内容区增加 32px 桌面内边距,表单阅读上限调整为 560px;个人资料头像预览调整为 56px;“添加其他人”移动到“其他人”分组标题操作区。保留四分区共享 `.settings-modal` 和 `vh` + `@supports (height: 1dvh)` 回退契约。
|
||||
- 验证:`./node_modules/.bin/tsc --noEmit` 安装依赖后退出码 0;`npm run lint` 退出码 0(0 errors、120 warnings);设置相关定向测试 21/21 通过。广泛测试筛选命令为 3481 tests / 3402 pass / 79 fail,失败集中在重复迁移、Windows 路径、Docker/数据库和技能 symlink 等环境或基线问题,不能作为本轮 UI 回归归因。`npm run build` 已完成编译和 TypeScript,但在收集 `/api/daily-starlanguage` 页面数据时因 Windows 禁止创建 Skill runtime symlink(`EPERM`)失败,因此尚未确认 `/` Static 或首屏 gzip;登录态浏览器走查仍待受控环境。
|
||||
- 防复发:设置导航继续使用 icon + label 的两列结构且不添加页面跳转箭头;右侧滚动容器负责内边距,表单 cap 只作用于其子内容;列表视图默认不渲染表单,添加入口固定在“其他人”标题操作区;`frontend/DESIGN.md` 与合同测试同步锁定这些关系。
|
||||
- 相关记录:BUG-554、BUG-698
|
||||
- 复发自:无
|
||||
- 修复版本:待发布
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
# 设置面板 UI 改造进度
|
||||
|
||||
- 日期:2026-09-19
|
||||
- 基线:`origin/staging` / `4f4cd684`
|
||||
- 分支:`codex/settings-ui-20260919`
|
||||
- 范围:设置弹窗布局、个人资料、星盘资料列表入口;不修改账户 API、星盘 API、数据库或工作流。
|
||||
|
||||
## 已完成
|
||||
|
||||
- 设置弹窗保留四个分区共用 `.settings-modal`,桌面左侧导航从 176px 调整为 200px,并增加导航与内容之间的 hairline 分隔。
|
||||
- 设置导航移除误导性的尾部右箭头,保留图标 + 分区名称;标题增加“设置”眉标题并保留当前分区标题。
|
||||
- 右侧内容区增加桌面 32px 内边距,表单阅读宽度上限从 440px 调整为 560px;移动端使用 16px 内容内边距。
|
||||
- 保留 `vh` 基线与 `@supports (height: 1dvh)` 升级,不回退到会被压缩器吃掉的重复声明写法。
|
||||
- 个人资料头像文案改为“账户头像”,Beam 说明收紧,预览从 48px 改为 56px;同步桌面与移动端 CSS。
|
||||
- “添加其他人”移动到“其他人”分组标题右侧,列表继续默认不展开表单。
|
||||
- 更新 `frontend/DESIGN.md`、设置布局合同测试、资料面板测试和星盘资料合同测试。
|
||||
|
||||
## 断言变更记录
|
||||
|
||||
| 文件 | 原值 | 新值 | 原因 |
|
||||
| --- | --- | --- | --- |
|
||||
| `frontend/tests/profile-panel.test.ts` | `size={48}` | `size={56}` | 头像预览需要更清晰的视觉锚点,并与账户身份区留出稳定间距。 |
|
||||
| `frontend/tests/account-dialog-overlay.test.ts` | 表单 cap 420–460px | `max-width: 560px` | 内容区已有明确内边距,资料表单需要舒适阅读宽度,避免右侧留下过大的空白。 |
|
||||
|
||||
## 测试结果
|
||||
|
||||
已执行结果:
|
||||
|
||||
- `npm run lint`:退出码 0,120 warnings、0 errors;警告与本轮改动无关,未顺手修改。
|
||||
- `./node_modules/.bin/tsc --noEmit`:安装当前 worktree 依赖后无输出并以退出码 0 结束。
|
||||
- 设置相关定向测试:21 tests / 21 pass / 0 fail,覆盖设置导航、固定外框、两列导航、内容内边距、560px 表单 cap、头像 56px、星盘资料列表入口与详情动作。
|
||||
- 全量 `npm test -- --test-name-pattern=...`:退出码 1,3481 tests / 3402 pass / 79 fail;失败集中包含重复迁移文件、Windows 路径拼接、Docker/数据库和技能 symlink 权限等环境或基线问题,不能宣称全量通过,需与基线逐条比对。
|
||||
- `npm run build`:编译与 TypeScript 阶段通过,但在收集 `/api/daily-starlanguage` 页面数据时因 Windows 禁止创建 Skill runtime symlink 失败;这是环境权限缺口,不是本轮设置 UI 编译错误。构建未形成可用于确认 `/` Static 或首屏 gzip 的完整产物。
|
||||
|
||||
## 浏览器验收环境缺口
|
||||
|
||||
尚未进行登录态下的浏览器截图级走查。需要受控账号、Chrome/真实设备和真实设置内容,按 `docs/testing/settings-dialog-20260916.md` 复核四分区切换、深色主题、移动端四栏、个人资料头像和星盘资料列表。没有受控登录态或 Chrome 时,不把该项写成通过。
|
||||
|
||||
## 未完成事项
|
||||
|
||||
- 浏览器/真实设备验收仍待具备受控登录态与 Chrome 的环境执行;BUG-970 继续保持 `investigating`。
|
||||
- 构建因 Windows Skill runtime symlink 权限缺口未完成页面数据收集,需在允许 symlink 的环境复跑,才能确认 `/` Static 与首屏 gzip。
|
||||
- 完整测试的 79 个失败需在可用环境与基线逐条比对;本记录不把它们归因于本轮 UI 改动。
|
||||
@@ -283,6 +283,7 @@
|
||||
| `TASK-sidebar-unify-20260916.md` | `PROGRESS-sidebar-unify-20260916.md` | **侧栏统一成一个组件 + 次级页共享外壳 + 跳转不整页刷新(纯前端)**:`/` 用 `AppSidebar`、四个次级页用另写的 `AppNavRail`,两套标记结构共用一份 CSS,会话行少 `.session-main` 一层(无内边距 / 44px 高 / 当前项标记、留 44px 空列)、页脚 56px 菜单 vs 44px 文字;首页进次级页是 `window.location.assign` 整页刷新,次级页各自在组件里挂 `SecondaryShell`、无共享 layout 无缓存,每次跳转重拉 `/api/sessions` + `/api/account`;折叠状态不持久。产品拍板 D1 只留一个 `AppSidebar`(操作回调可选 = 只读模式,D9「不带写操作」维持)、D2 路由组 `app/(secondary)/` layout 承载外壳、D3 改 `<Link>`、D4 模块级 60 s 缓存 + 首页写操作失效、D5 折叠状态存 cookie、D6 回首页慢的启动链另开一单。实测:接口 TTFB 0.63~0.90 s,回 `/` 启动链串行 ≥4 次往返 + 揭幕闸门 4 s。BUG-744~746 | 已验收(2026-09-16,Opus 子代理实现 `22edabbc`,门禁全绿、失败清单与基线逐条相同;折叠状态取 localStorage 触发让步 1;真机清单待走) | `codex/sidebar-unify-20260916` |
|
||||
| `TASK-rectification-precision-gate-guided-collect-fix-20260916.md` | `PROGRESS-rectification-precision-gate-guided-collect-fix-20260916.md` | **验收修复单**:F1 `decision_receipt` 新增 `guided_collect_windows` 让 `test_rectification_engine_memoization` golden 红(在 CORE_PYTEST_TARGETS,staging 未部署 cfb41daf);F2 16 条既有校正断言红未按三栏改;F3 `mayDeliverOnPrecision` 缺省即放行、短路 BUG-654 且 `guidedCollectExhausted` 不含 refresh;F4 无领域轨道窗口轮询贴领域、一窗只问一领域;F5 离线回放注入 month_lo 非真值方向、0/20 不作数;F6 录入卡合成「或」列表句走模型轮;F7 page.tsx +1、手造 fixture。D6 产品已拍板:门槛只是必要条件,仍问完线再出。BUG 段 744 起 | **已验收通过,已合入 staging**;合入后 run `2697` 仍红 capability audit(`(secondary)/chart`),跟进 BUG-753 | `dc732825`(Opus 子代理实现,Claude 独立验收):tsc 0 / lint 0 error / npm test 3405 条 31 红与基线 a396bbe0 逐条一致(16 条校正红转绿、0 新红)/ pytest 193 通过 / `○ /` Static / gzip 582,552 → 582,800(+0.04%)/ page.tsx 1836 → 1835 / golden diff 仅加 `guided_collect_windows` 一键 / Skill 10.0.28。F5 真值方向回放三档仍 0 例靠引导件达标,作研究结论记录。D2+D6 合并后 10 分钟门槛只上报不改出卡时机 |
|
||||
| — (产品口头拍板,无任务书) | `PROGRESS-agent-voice-20260917.md` | **对话口气改形状(纯提示词与文案,无 BUG 号)**:产品判定现有人设出来的是顾问报告。人设改成「把人当一个人认真对待 / 行动力很强、嘴有点毒但靠谱的同事」;开场从「一句结论 + 2–3 条短要点 + 一句下一步」换成固定形状——**反差**(表面 A 底下 B,命名成一个格局)→ **谁在推谁在修**(大运主星推、行运修体面)→ **别去应 X 的象,去扮演 Y 的象** → **最多三条短行动**(各 ≤ 20 字),仍无标题、≤ 400 字。新增希望纪律:有转机且允许精确应期就说到月份,没有就说这段时间拿来干什么、能扮演哪个象,禁「一切都会好 / 相信自己 / 加油 / 你值得更好的 / 宇宙自有安排」。申报时段与无出生分钟两条降级路线形状照给、不编月份。改 `product-voice.ts` / `consult/route.ts` 用户回合文案 / 三处复述旧形状的系统指令 / `VOICE.md`(五条原则→七条)/ `CHANGELOG.md`。零业务逻辑改动,Skill 版本不变 | 已实现,待验收 | `codex/agent-voice-20260917`:tsc 0 / lint 0 error(118 warning 不变)/ npm test 3468→3471 条、36 红与基线 `ff0427cf` 逐条相同、0 新红 / `○ /` Static / 首屏 gzip 1,416,965 两侧**字节相同**(改动全在服务端模块,客户端 chunk 无该文本)。真机口气走查欠 |
|
||||
| — (产品口头拍板,无任务书) | `PROGRESS-settings-ui-20260919.md` | **设置面板布局与资料入口整理**:基线 `4f4cd684`;四分区继续共用固定 `.settings-modal`,桌面导航 176px→200px 并加分隔,内容区增加内边距,表单 cap 440px→560px,导航移除误导性右箭头;账户头像 48px→56px;“添加其他人”移到分组标题操作区。已同步 `frontend/DESIGN.md` 与合同测试;tsc 0、lint 0 error、定向测试 21/21;build 被 Windows Skill runtime symlink 权限阻塞,浏览器走查待受控环境;BUG-970 保持 `investigating` | 待验收 | `codex/settings-ui-20260919` |
|
||||
|
||||
## 命名与归档
|
||||
|
||||
|
||||
+6
-6
@@ -533,12 +533,12 @@ page. Three parts now, in reading order:
|
||||
### Settings dialog
|
||||
|
||||
- **Placement:** the overlay and the onboarding paywall portal to `document.body`. `SidebarInset` stays `inert` while a dialog is open (the BUG-744~746 focus contract); the dialog itself must not sit inside that subtree (BUG-968). Closing still uses the existing overlay click, the header button, and the window-level Escape listener.
|
||||
- **Structure:** one fixed chrome for four panes — 个人资料, 星盘资料, 账户与点数, 通用设置. Left nav is 176px and does not scroll; the title bar stays put; only the right-hand content pane scrolls. Logout stays a separate 400px confirmation.
|
||||
- **Width / height:** desktop `width: min(100vw - 32px, 880px)`, with `height: min(84vh, 640px)` as the base and `min(84dvh, 640px)` applied inside `@supports (height: 1dvh)`. All four panes share one class (`.settings-modal`), so switching panes cannot change the frame. At ≤767px the dialog is full-screen with four equal tabs along the top.
|
||||
- **Pane menu states:** default is transparent with secondary ink; hover is a 55% wash of `--color-canvas-muted` keeping secondary ink; current is the solid muted surface with primary ink. No accent bar, and no weight change — hierarchy here comes from ink rank and surface, matching “Hierarchy inside the nav comes from ink rank, not hue”. The sidebar's 2px `--sidebar-ring` on the active session is deliberately **not** changed to match; the two surfaces read differently on purpose until that is revisited.
|
||||
- **Content width:** the 880px frame leaves roughly 690px of content, which is too wide for a single column of fields. Form panes (个人资料, 通用设置) cap their children at 440px and stay left aligned (`.settings-dialog-content--form > *`); list panes (星盘资料, 账户与点数) stay full-bleed so tables and card grids keep their columns. The cap sits on the children of the scroll container only — never on `.settings-modal` or `.account-settings-shell` — so it cannot make the frame resize between panes (BUG-554 / BUG-698).
|
||||
- **Personal profile:** one row of avatar editing (48px preview, eight palettes, 换一个形象) plus nickname and login email. No duplicate 管理星盘资料 button.
|
||||
- **Chart library:** list first (self row, other rows, 添加其他人). A row opens a detail with ← 星盘资料. Other details own 设为默认 / 删除 / 用于合盘 and that person's synastry history. The add form is a view, not an always-on stack.
|
||||
- **Structure:** one fixed chrome for four panes — 个人资料, 星盘资料, 账户与点数, 通用设置. Left nav is 200px, has a hairline boundary, and does not scroll; the title bar stays put; only the right-hand content pane scrolls. The pane menu uses icon + label rows without a trailing chevron because these controls switch content inside the same dialog. Logout stays a separate 400px confirmation.
|
||||
- **Width / height:** desktop `width: min(100vw - 32px, 880px)`, with `height: min(84vh, 640px)` as the base and `min(84dvh, 640px)` applied inside `@supports (height: 1dvh)`. The right content pane uses 32px horizontal and bottom inset on desktop. All four panes share one class (`.settings-modal`), so switching panes cannot change the frame. At ≤767px the dialog is full-screen with four equal tabs along the top and 16px content inset.
|
||||
- **Pane menu states:** default is transparent with secondary ink; hover is a 55% wash of `--color-canvas-muted` keeping secondary ink; current is the solid muted surface with primary ink. No accent bar, no trailing navigation chevron, and no weight change — hierarchy here comes from ink rank and surface, matching “Hierarchy inside the nav comes from ink rank, not hue”. The sidebar's 2px `--sidebar-ring` on the active session is deliberately **not** changed to match; the two surfaces read differently on purpose until that is revisited.
|
||||
- **Content width:** the 880px frame now gives the content pane a deliberate inset instead of leaving controls against the divider. Form panes (个人资料, 通用设置) cap their children at 560px and stay left aligned (`.settings-dialog-content--form > *`); list panes (星盘资料, 账户与点数) stay full-bleed within the inset so tables and card grids keep their columns. The cap sits on the children of the scroll container only — never on `.settings-modal` or `.account-settings-shell` — so it cannot make the frame resize between panes (BUG-554 / BUG-698).
|
||||
- **Personal profile:** one row of avatar editing (56px preview, eight palettes, 换一个形象) plus nickname and login email. The header uses the product-level “设置” eyebrow above the current pane title; there is no duplicate 管理星盘资料 button.
|
||||
- **Chart library:** list first (self row, other rows, 添加其他人). 添加其他人 sits in the 其他人 group heading action area instead of below the list, so the entry remains discoverable when the list grows. A row opens a detail with ← 星盘资料. Other details own 设为默认 / 删除 / 用于合盘 and that person's synastry history. The add form is a view, not an always-on stack.
|
||||
- **States:** open, pane switch, list / self / other / add, saving, success, and error.
|
||||
|
||||
### Billing pane
|
||||
|
||||
@@ -1831,23 +1831,21 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.account-modal-overlay { position: fixed; z-index: 40; inset: 0; display: grid; place-items: center; padding: var(--space-4); background: var(--color-scrim); animation: account-overlay-enter 180ms ease-out both; }
|
||||
.account-modal { width: min(100%, 560px); max-height: min(84vh, 760px); overflow-y: auto; padding: var(--space-8); border: 1px solid var(--color-border); border-radius: var(--radius-xl); background: var(--color-canvas); box-shadow: var(--shadow-elevated); animation: account-dialog-enter 180ms var(--ease-out) both; }
|
||||
.settings-modal { width: min(100vw - 32px, 880px); height: min(84vh, 640px); max-height: min(84vh, 640px); overflow: hidden; display: grid; grid-template-rows: auto 1fr; }
|
||||
.account-settings-shell { display: grid; grid-template-columns: 176px minmax(0, 1fr); min-height: 0; overflow: hidden; align-items: stretch; }
|
||||
.settings-dialog-nav { display: grid; align-content: start; gap: var(--space-1); overflow: hidden; }
|
||||
.settings-dialog-nav-item { min-height: 44px; display: grid; grid-template-columns: 18px minmax(0, 1fr) 16px; align-items: center; gap: var(--space-2); padding: 0 var(--space-3); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--color-ink-secondary); cursor: pointer; text-align: left; }
|
||||
.account-settings-shell { display: grid; grid-template-columns: 200px minmax(0, 1fr); min-height: 0; overflow: hidden; align-items: stretch; }
|
||||
.settings-dialog-nav { display: grid; align-content: start; gap: var(--space-1); overflow: hidden; padding-right: var(--space-4); border-right: 1px solid var(--color-border); }
|
||||
.settings-dialog-nav-item { min-height: 44px; display: grid; grid-template-columns: 18px minmax(0, 1fr); align-items: center; gap: var(--space-2); padding: 0 var(--space-3); border: 0; border-radius: var(--radius-md); background: transparent; color: var(--color-ink-secondary); cursor: pointer; text-align: left; }
|
||||
.settings-dialog-nav-item:hover { background: color-mix(in srgb, var(--color-canvas-muted) 55%, transparent); color: var(--color-ink-secondary); }
|
||||
.settings-dialog-nav-item[aria-current="page"] { background: var(--color-canvas-muted); color: var(--color-ink); }
|
||||
.settings-dialog-nav-item > svg { width: 18px; height: 18px; color: var(--color-ink-tertiary); }
|
||||
.settings-dialog-nav-item > svg:last-child { width: 15px; height: 15px; margin-left: auto; }
|
||||
.settings-dialog-content { min-width: 0; min-height: 0; overflow-y: auto; }
|
||||
/* Form panes (个人资料 / 通用设置) read as one column, so they are capped and left
|
||||
aligned instead of stretching across the ~690px content area. The cap lives on the
|
||||
children, never on the scroll container or the dialog box: .settings-modal keeps its
|
||||
fixed width/height, so the four panes still cannot change the dialog size (BUG-554 /
|
||||
BUG-698). List panes (星盘资料 / 账户与点数) stay full-bleed. */
|
||||
.settings-dialog-content--form > * { max-width: 440px; margin-right: auto; }
|
||||
.settings-dialog-copy { margin: 0 0 var(--space-5); color: var(--color-ink-secondary); font-size: var(--type-body-sm); line-height: 1.6; }
|
||||
.settings-dialog-content { min-width: 0; min-height: 0; overflow-y: auto; padding: 0 var(--space-8) var(--space-8); }
|
||||
/* Form panes (个人资料 / 通用设置) use a comfortable reading width instead of
|
||||
leaving the content stranded in a narrow 440px column. The cap stays on pane
|
||||
children, never on the scroll container or dialog box, so pane switches keep the
|
||||
same frame (BUG-554 / BUG-698). List panes remain full-bleed within this inset. */
|
||||
.settings-dialog-content--form > * { max-width: 560px; margin-right: auto; }
|
||||
.settings-dialog-copy { max-width: 760px; margin: 0 0 var(--space-5); color: var(--color-ink-secondary); font-size: var(--type-body-sm); line-height: 1.6; }
|
||||
.avatar-section { padding-top: 0; }
|
||||
.avatar-editor { display: grid; grid-template-columns: 48px minmax(0, 1fr); align-items: center; gap: var(--space-5); margin-top: var(--space-5); }
|
||||
.avatar-editor { display: grid; grid-template-columns: 56px minmax(0, 1fr); align-items: center; gap: var(--space-5); margin-top: var(--space-5); }
|
||||
.avatar-editor-controls { min-width: 0; display: grid; gap: var(--space-4); }
|
||||
.avatar-palette-list { display: grid; grid-template-columns: repeat(4, minmax(48px, 1fr)); gap: var(--space-2); }
|
||||
.avatar-palette { min-width: 0; height: 34px; overflow: hidden; display: flex; padding: 3px; border: 1px solid var(--color-border); border-radius: var(--radius-md); background: var(--color-canvas); cursor: pointer; transition: border-color 120ms ease-out, box-shadow 120ms ease-out, transform 120ms ease-out; }
|
||||
@@ -1864,6 +1862,8 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.logout-modal { width: min(100%, 400px); }
|
||||
.account-modal h2, .auth-panel h1 { font-family: var(--font-display); font-weight: 500; letter-spacing: -.5px; text-wrap: balance; }
|
||||
.account-modal h2 { margin: 0; font-size: var(--type-display-sm); letter-spacing: -.025em; }
|
||||
.account-modal-heading { min-width: 0; display: grid; gap: var(--space-1); }
|
||||
.account-modal-eyebrow { color: var(--color-ink-tertiary); font-size: var(--type-overline); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; }
|
||||
.account-modal-header { display: flex; align-items: flex-start; justify-content: space-between; gap: var(--space-4); padding-bottom: var(--space-5); }
|
||||
.dialog-close { width: 44px; height: 44px; display: grid; flex: 0 0 auto; place-items: center; border: 0; cursor: pointer; transition: background-color 120ms ease-out, transform 120ms ease-out; border-radius: var(--radius-md); background: var(--color-canvas-muted); }
|
||||
.redeem-balance { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-4); padding: var(--space-4) 0; border-top: 1px solid var(--color-border); border-bottom: 1px solid var(--color-border); }
|
||||
@@ -1890,6 +1890,8 @@ button:disabled { cursor: default; opacity: .45; }
|
||||
.chart-library-panel { display: grid; gap: var(--space-5); margin-top: var(--space-5); }
|
||||
.chart-library-group { display: grid; gap: var(--space-3); }
|
||||
.chart-library-group-heading { display: flex; align-items: center; justify-content: space-between; gap: var(--space-3); }
|
||||
.chart-library-group-heading > div:first-child { min-width: 0; }
|
||||
.chart-library-group-heading-actions { display: flex; align-items: center; flex: 0 0 auto; gap: var(--space-2); }
|
||||
.chart-library-group-heading b, .chart-library-group-heading small { display: block; }
|
||||
.chart-library-group-heading b { color: var(--color-ink); font-size: var(--type-caption); font-weight: 600; }
|
||||
.chart-library-group-heading small { margin-top: 3px; color: var(--color-ink-secondary); font-size: var(--type-caption); font-weight: 400; }
|
||||
@@ -1899,6 +1901,7 @@ button.chart-library-item { cursor: pointer; text-align: left; }
|
||||
.chart-library-item-chevron { width: 16px; height: 16px; flex: 0 0 auto; color: var(--color-ink-tertiary); }
|
||||
.chart-library-back { min-height: 44px; display: inline-flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-4); padding: 0; border: 0; background: transparent; color: var(--color-ink-secondary); cursor: pointer; font-size: var(--type-body-sm); }
|
||||
.chart-library-add { margin-top: var(--space-4); }
|
||||
.chart-library-add--heading { margin-top: 0; white-space: nowrap; }
|
||||
.chart-library-item-main { min-width: 0; }
|
||||
.chart-library-item strong, .chart-library-item small { display: block; }
|
||||
.chart-library-item strong { color: var(--color-ink); font-size: var(--type-body-md); font-weight: 500; }
|
||||
@@ -2025,13 +2028,16 @@ input:not([type="radio"]):not([type="checkbox"]):not([class^="ant-"]):not([class
|
||||
.account-modal { width: 100%; max-height: 100vh; min-height: 100vh; border-radius: 0; padding: var(--space-6); }
|
||||
.settings-modal { width: 100%; height: 100vh; max-height: 100vh; }
|
||||
.account-settings-shell { grid-template-columns: 1fr; grid-template-rows: auto 1fr; gap: var(--space-4); }
|
||||
.settings-dialog-nav { position: static; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-1); padding-bottom: var(--space-3); border-bottom: 1px solid var(--color-border); }
|
||||
.settings-dialog-nav-item { min-height: 40px; grid-template-columns: 1fr; justify-items: center; gap: 2px; padding: var(--space-2); text-align: center; font-size: var(--type-caption); }
|
||||
.settings-dialog-nav-item > svg:last-child { display: none; }
|
||||
.settings-dialog-content { min-height: 0; overflow-y: auto; }
|
||||
.avatar-editor { grid-template-columns: 48px minmax(0, 1fr); gap: var(--space-4); }
|
||||
.avatar-editor > .user-avatar { width: 48px !important; height: 48px !important; }
|
||||
.settings-dialog-nav { position: static; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--space-1); padding-bottom: var(--space-3); border-right: 0; border-bottom: 1px solid var(--color-border); }
|
||||
.settings-dialog-nav-item { min-height: 44px; grid-template-columns: 1fr; justify-items: center; gap: 2px; padding: var(--space-2); text-align: center; font-size: var(--type-caption); }
|
||||
.settings-dialog-content { min-height: 0; overflow-y: auto; padding: 0 var(--space-4) var(--space-6); }
|
||||
.settings-dialog-content--form > * { max-width: none; }
|
||||
.avatar-editor { grid-template-columns: 56px minmax(0, 1fr); gap: var(--space-4); }
|
||||
.avatar-editor > .user-avatar { width: 56px !important; height: 56px !important; }
|
||||
.avatar-palette-list { grid-template-columns: repeat(2, minmax(64px, 1fr)); }
|
||||
.chart-library-group-heading { align-items: flex-start; }
|
||||
.chart-library-group-heading-actions { align-items: flex-end; flex-direction: column; gap: var(--space-1); }
|
||||
.chart-library-add--heading { min-height: 40px; padding-inline: var(--space-3); }
|
||||
.auth-page { height: 100vh; overflow-x: hidden; overflow-y: auto; -webkit-overflow-scrolling: touch; padding: 0; }
|
||||
.auth-shell { min-height: 100%; overflow: visible; align-content: start; grid-template-columns: 1fr; grid-template-rows: auto auto; border-radius: 0; box-shadow: none; }
|
||||
.auth-story { min-height: 0; justify-content: flex-start; gap: var(--space-3); padding: var(--space-6) var(--space-6) var(--space-5); }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { ChevronRight, Settings, UserRound, Users, WalletCards, X } from "lucide-react";
|
||||
import { Settings, UserRound, Users, WalletCards, X } from "lucide-react";
|
||||
import { memo, type MutableRefObject, type ReactNode } from "react";
|
||||
import { createPortal } from "react-dom";
|
||||
|
||||
@@ -72,7 +72,10 @@ export const AccountDialogOverlay = memo(function AccountDialogOverlay({
|
||||
onMouseDown={(event) => event.stopPropagation()}
|
||||
>
|
||||
<header className="account-modal-header">
|
||||
<h2 id="account-dialog-title">{model.title}</h2>
|
||||
<div className="account-modal-heading">
|
||||
{isSettingsDialog ? <span className="account-modal-eyebrow">设置</span> : null}
|
||||
<h2 id="account-dialog-title">{model.title}</h2>
|
||||
</div>
|
||||
<button
|
||||
className="dialog-close"
|
||||
ref={model.closeButtonRef}
|
||||
@@ -97,7 +100,6 @@ export const AccountDialogOverlay = memo(function AccountDialogOverlay({
|
||||
>
|
||||
<Icon aria-hidden="true" />
|
||||
<span>{label}</span>
|
||||
<ChevronRight aria-hidden="true" />
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
|
||||
@@ -323,24 +323,26 @@ export function ChartLibraryPanel({
|
||||
<div className="chart-library-group">
|
||||
<div className="chart-library-group-heading">
|
||||
<div><b>其他人</b><small>亲友、伴侣或客户资料</small></div>
|
||||
<span className="chart-library-count">{otherCharts.length}</span>
|
||||
<div className="chart-library-group-heading-actions">
|
||||
<span className="chart-library-count">{otherCharts.length}</span>
|
||||
<button
|
||||
className="button-secondary chart-library-add chart-library-add--heading"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setOtherProfileDraft(emptyProfile);
|
||||
setOtherChartRelationship("other");
|
||||
setEditingChartId(null);
|
||||
setAccountError("");
|
||||
setProfileNotice("");
|
||||
setView({ kind: "add" });
|
||||
}}
|
||||
>
|
||||
添加其他人
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{otherCharts.length === 0 && <p className="empty-library-copy">还没有其他星盘,先添加一份资料。</p>}
|
||||
{otherCharts.map(listRow)}
|
||||
<button
|
||||
className="button-secondary chart-library-add"
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setOtherProfileDraft(emptyProfile);
|
||||
setOtherChartRelationship("other");
|
||||
setEditingChartId(null);
|
||||
setAccountError("");
|
||||
setProfileNotice("");
|
||||
setView({ kind: "add" });
|
||||
}}
|
||||
>
|
||||
添加其他人
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -27,11 +27,11 @@ export function ProfilePanel({
|
||||
{account.avatar && (
|
||||
<section className="sheet-section avatar-section" aria-labelledby="avatar-section-title">
|
||||
<div className="section-heading">
|
||||
<b id="avatar-section-title">头像</b>
|
||||
<small>Beam 形象由随机种子生成,刷新和换设备后保持一致</small>
|
||||
<b id="avatar-section-title">账户头像</b>
|
||||
<small>Beam 形象会在刷新和换设备后保持一致</small>
|
||||
</div>
|
||||
<div className="avatar-editor">
|
||||
<UserAvatar avatar={account.avatar} size={48} label="当前头像预览" />
|
||||
<UserAvatar avatar={account.avatar} size={56} label="当前头像预览" />
|
||||
<div className="avatar-editor-controls">
|
||||
<div className="avatar-palette-list" role="radiogroup" aria-label="头像配色">
|
||||
{beamAvatarPalettes.map((palette, index) => (
|
||||
|
||||
@@ -127,6 +127,15 @@ test("the settings pane menu separates hover from current without an accent bar"
|
||||
for (const rule of hoverRules) assert.ok(!rule.includes('[aria-current="page"]'), `current and hover must be separate rules: ${rule}`);
|
||||
});
|
||||
|
||||
test("settings navigation uses two columns without a misleading chevron", () => {
|
||||
const overlay = readFileSync(new URL("../src/components/account-dialog-overlay.tsx", import.meta.url), "utf8");
|
||||
const styles = readFileSync(new URL("../src/app/globals.css", import.meta.url), "utf8");
|
||||
assert.doesNotMatch(overlay, /<ChevronRight/);
|
||||
assert.match(cssDeclarations(".settings-dialog-nav-item", styles), /grid-template-columns:\s*18px\s+minmax\(0,\s*1fr\)/);
|
||||
assert.match(cssDeclarations(".settings-dialog-nav", styles), /border-right:\s*1px\s+solid\s+var\(--color-border\)/);
|
||||
assert.match(cssDeclarations(".settings-dialog-content", styles), /padding:\s*0\s+var\(--space-8\)\s+var\(--space-8\)/);
|
||||
});
|
||||
|
||||
test("form panes get a reading-width cap, list panes stay full-bleed", () => {
|
||||
// T8.1 / E13: the 880px dialog leaves ~690px of content, which pulls a one-column
|
||||
// form apart. The cap is applied per pane, inside the content box.
|
||||
@@ -149,7 +158,8 @@ test("form panes get a reading-width cap, list panes stay full-bleed", () => {
|
||||
}
|
||||
|
||||
const cap = cssDeclarations(".settings-dialog-content--form > *", styles);
|
||||
assert.match(cap, /max-width:\s*4[2-6]\dpx/, "cap belongs in the 420-460px reading-width band");
|
||||
// 原值:420–460px;新值:560px;原因:右侧内容区增加内边距后,资料表单仍需使用舒适的阅读宽度,避免右侧留下过大的空白。
|
||||
assert.match(cap, /max-width:\s*560px/, "cap uses the wider 560px reading width");
|
||||
assert.match(cap, /margin-right:\s*auto/, "capped content is left aligned");
|
||||
});
|
||||
|
||||
|
||||
@@ -110,6 +110,8 @@ test("the chart library starts as a list and only renders a form after a view ch
|
||||
assert.match(charts, /useState<ChartLibraryView>\(CHART_LIBRARY_LIST_VIEW\)/);
|
||||
assert.doesNotMatch(list, /ChartProfileForm|<form/);
|
||||
assert.match(list, /添加其他人/);
|
||||
// 原值:仅断言列表中存在“添加其他人”;新值:同步锁定标题操作区;原因:入口从列表末尾移到“其他人”分组标题,长列表中位置仍稳定。
|
||||
assert.match(charts, /chart-library-group-heading-actions[\s\S]{0,900}添加其他人/);
|
||||
assert.match(charts, /setView\(\{ kind: "add" \}\)/);
|
||||
assert.match(charts, /setView\(CHART_LIBRARY_LIST_VIEW\)/);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,8 @@ test("the profile pane keeps avatar palettes and drops the duplicate chart-libra
|
||||
assert.doesNotMatch(panel, /管理星盘资料/);
|
||||
assert.match(panel, /role="radiogroup"/);
|
||||
assert.match(panel, /换一个形象/);
|
||||
assert.match(panel, /size=\{48\}/);
|
||||
// 原值:48px;新值:56px;原因:设置面板的头像预览需要更清晰的视觉锚点,并与账户身份区留出稳定间距。
|
||||
assert.match(panel, /size=\{56\}/);
|
||||
assert.match(panel, /账户信息/);
|
||||
assert.match(panel, /登录邮箱/);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user