From eb8f5147f443d1bec8779a003345e09f5cf0407b Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Sat, 19 Sep 2026 11:28:24 +0800 Subject: [PATCH] fix(frontend): refine settings dialog layout Co-Authored-By: Claude Code --- CHANGELOG.md | 4 ++ docs/BUG_HISTORY.md | 16 +++++++ docs/tasks/PROGRESS-settings-ui-20260919.md | 43 ++++++++++++++++++ docs/tasks/README.md | 1 + frontend/DESIGN.md | 12 ++--- frontend/src/app/globals.css | 44 +++++++++++-------- .../src/components/account-dialog-overlay.tsx | 8 ++-- .../src/components/chart-library-panel.tsx | 32 +++++++------- frontend/src/components/profile-panel.tsx | 6 +-- frontend/tests/account-dialog-overlay.test.ts | 12 ++++- .../tests/chart-library-other-profile.test.ts | 2 + frontend/tests/profile-panel.test.ts | 3 +- 12 files changed, 135 insertions(+), 48 deletions(-) create mode 100644 docs/tasks/PROGRESS-settings-ui-20260919.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 17a1b4b2..d6ceb86e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 印度占星 Skill 更新日志 +## 2026-09-19 — 设置弹窗布局与资料入口整理 + +设置弹窗的四个分区继续共用固定外框,桌面导航加宽并与内容区分隔,右侧内容增加稳定内边距,表单阅读宽度更舒适;导航去掉会误导成页面跳转的右箭头。个人资料的账户头像预览放大到 56px,星盘资料的“添加其他人”移到分组标题旁,列表较长时也容易找到。Skill 版本不变(纯界面改动)。 + ## 2026-09-18 — 账户弹窗重新能点;校正读不到下一题时会说出来 侧栏打开个人资料、星盘资料、设置、点数或退出登录时,弹窗里的按钮和表单又能点了,确认退出也恢复了。生时校正如果没读到下一题,会写「这一问还没读到。」并给出「重新读取」,不再停在空白里装没事。Skill 版本不变。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index fa88bd55..fdc2d27f 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -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 +- 复发自:无 +- 修复版本:待发布 diff --git a/docs/tasks/PROGRESS-settings-ui-20260919.md b/docs/tasks/PROGRESS-settings-ui-20260919.md new file mode 100644 index 00000000..573b7738 --- /dev/null +++ b/docs/tasks/PROGRESS-settings-ui-20260919.md @@ -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 改动。 diff --git a/docs/tasks/README.md b/docs/tasks/README.md index cd6dd4b3..c3a13467 100644 --- a/docs/tasks/README.md +++ b/docs/tasks/README.md @@ -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 改 ``、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` | ## 命名与归档 diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 7caf5ac4..ad81b0df 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -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 diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index 2448db66..b2f4cf33 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -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); } diff --git a/frontend/src/components/account-dialog-overlay.tsx b/frontend/src/components/account-dialog-overlay.tsx index 0c83ecac..9d9a3ada 100644 --- a/frontend/src/components/account-dialog-overlay.tsx +++ b/frontend/src/components/account-dialog-overlay.tsx @@ -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()} >
-

{model.title}

+
+ {isSettingsDialog ? 设置 : null} +

{model.title}

+
))} diff --git a/frontend/src/components/chart-library-panel.tsx b/frontend/src/components/chart-library-panel.tsx index 33ffb299..b96bf768 100644 --- a/frontend/src/components/chart-library-panel.tsx +++ b/frontend/src/components/chart-library-panel.tsx @@ -323,24 +323,26 @@ export function ChartLibraryPanel({
其他人亲友、伴侣或客户资料
- {otherCharts.length} +
+ {otherCharts.length} + +
{otherCharts.length === 0 &&

还没有其他星盘,先添加一份资料。

} {otherCharts.map(listRow)} -
); diff --git a/frontend/src/components/profile-panel.tsx b/frontend/src/components/profile-panel.tsx index 4f52d4a2..5a0a34a0 100644 --- a/frontend/src/components/profile-panel.tsx +++ b/frontend/src/components/profile-panel.tsx @@ -27,11 +27,11 @@ export function ProfilePanel({ {account.avatar && (
- 头像 - Beam 形象由随机种子生成,刷新和换设备后保持一致 + 账户头像 + Beam 形象会在刷新和换设备后保持一致
- +
{beamAvatarPalettes.map((palette, index) => ( diff --git a/frontend/tests/account-dialog-overlay.test.ts b/frontend/tests/account-dialog-overlay.test.ts index 2ce39809..5fa0182c 100644 --- a/frontend/tests/account-dialog-overlay.test.ts +++ b/frontend/tests/account-dialog-overlay.test.ts @@ -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, / { // 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"); }); diff --git a/frontend/tests/chart-library-other-profile.test.ts b/frontend/tests/chart-library-other-profile.test.ts index a674ce42..72cd126d 100644 --- a/frontend/tests/chart-library-other-profile.test.ts +++ b/frontend/tests/chart-library-other-profile.test.ts @@ -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\(CHART_LIBRARY_LIST_VIEW\)/); assert.doesNotMatch(list, /ChartProfileForm|