From f9685f2837e30f3413da1ebb88fb77e2467b1659 Mon Sep 17 00:00:00 2001 From: jesse-ux Date: Mon, 21 Sep 2026 20:12:58 +0800 Subject: [PATCH] fix(web): paint session row highlight as one chip including the menu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Selected and hover fill stay on the whole row. The title and ⋯ no longer draw their own canvas or radius, so they stop looking like two controls (BUG-993, recurrence of BUG-024). --- CHANGELOG.md | 4 ++++ docs/BUG_HISTORY.md | 16 ++++++++++++++++ frontend/DESIGN.md | 2 +- frontend/src/app/globals.css | 8 ++++---- frontend/tests/sidebar-contract.test.ts | 8 ++++++++ 5 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56f9705b..6ed0d495 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # 印度占星 Skill 更新日志 +## 2026-09-21 — 侧栏当前会话是一整条高亮 + +选中或悬停会话时,标题和右边的「⋯」共用一块底,不再拆成两个块。Skill 版本不变。 + ## 2026-09-21 — 归档功能撤掉了,以前归档过的对话回到列表 会话菜单里不再有「归档」。以前归档过的对话会重新出现在侧栏,时间不会被改成今天。删除对话不受影响。Skill 版本不变。 diff --git a/docs/BUG_HISTORY.md b/docs/BUG_HISTORY.md index d2403302..a77cba1a 100644 --- a/docs/BUG_HISTORY.md +++ b/docs/BUG_HISTORY.md @@ -13151,3 +13151,19 @@ - 相关记录:BUG-933、BUG-934、BUG-939、BUG-991 - 复发自:BUG-939(Python 合同盯着已搬走的前端源码) - 修复版本:`d2cec179`(staging 已部署) + +## BUG-993 | 侧栏当前会话的标题和三点菜单又拆成两块高亮 + +- 状态:resolved +- 首次发现:2026-09-21 +- 最近更新:2026-09-21 +- 影响面:侧栏会话行选中态、`.session-menu-trigger` +- 用户现象:选中一条会话时,标题一块底、右边「⋯」另一块底,看起来像两个控件。 +- 触发条件:侧栏展开并选中任意带副标题的会话。 +- 根因:复发自 BUG-024。整行选中底已经在 `.session-row` 上,但 `.session-main` 仍有独立圆角,`.session-menu-trigger` 固定 44px 高并在悬停/展开时刷 `--color-canvas`。旧契约只锁了「不是 50% 圆」,锁不住这块独立画布。 +- 修复:选中/悬停只画在 `.session-row`;标题和「⋯」背景透明、无独立圆角;「⋯」拉高到整行。悬停不再铺 canvas。 +- 验证:`sidebar-contract.test.ts`「one unified row surface」补 overflow、标题圆角 0、trigger stretch、hover 不含 `--color-canvas`。 +- 防复发:会话标题和行内操作必须共享同一个行级状态面。不得给 `.session-menu-trigger` 单独的 canvas/选中底。 +- 相关记录:BUG-024 +- 复发自:BUG-024(旧测试没锁住 trigger 的 canvas 悬停底) +- 修复版本:待本修复合入 staging diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md index 80865f28..a4e9614a 100644 --- a/frontend/DESIGN.md +++ b/frontend/DESIGN.md @@ -357,7 +357,7 @@ The birth-time rectification session is the consultation transcript plus a house - **States:** default, hover, current, focus, disabled. - **Hierarchy:** section labels stay tertiary; session titles and primary actions use ink so history rows do not collapse into the same gray as “收藏对话 / 历史对话”. History groups use the overline token for “今天 / 昨天 / 最近 7 天 / 最近 30 天 / 更早”. When the chart is not the account holder, a secondary line shows the chart name under the title. A session title is named when it is created; opening a stored session must not rename it. `updatedAt` advances only on conversation activity, including rectification turns, choice, adopt, and stop. Opening, refresh, and metadata PATCH do not bump it. A `?c=` that is not on the loaded page is looked up with `GET /api/sessions/{id}` before anyone may say it was deleted. Locked by `frontend/tests/session-open-preserves-identity.test.ts` and `frontend/tests/session-lookup-unlisted.test.ts`. - **Request behavior:** existing sessions remain selectable for reading while a request is active; creating or sending another request stays locked until the active request settles. -- **Surface:** translucent warm-gray sidebar; current uses a white glass surface and deep-brown marker. +- **Surface:** translucent warm-gray sidebar. Current and hover fill the whole `.session-row` — title and the ⋯ share one chip. The menu trigger has no canvas of its own. ### Sidebar shell diff --git a/frontend/src/app/globals.css b/frontend/src/app/globals.css index b2f4cf33..0910deb5 100644 --- a/frontend/src/app/globals.css +++ b/frontend/src/app/globals.css @@ -816,10 +816,10 @@ button:disabled { cursor: default; opacity: .45; } .sidebar-empty { margin: 0; padding: 0 0 var(--space-2); color: var(--color-ink-tertiary); font-size: var(--type-caption); line-height: 1.35; } /* Visual chip stays 32px; wrap gap is 8px, so a 44px overlay would overlap. */ .session-list { min-height: 0; display: flex; flex-direction: column; gap: var(--space-1); } -.session-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 44px; align-items: center; border-radius: var(--radius-lg); color: var(--sidebar-foreground); transition: background-color 120ms ease-out, box-shadow 120ms ease-out, color 120ms ease-out; } +.session-row { position: relative; display: grid; grid-template-columns: minmax(0, 1fr) 44px; align-items: stretch; overflow: hidden; border-radius: var(--radius-lg); color: var(--sidebar-foreground); transition: background-color 120ms ease-out, box-shadow 120ms ease-out, color 120ms ease-out; } .session-row:hover, .session-row:focus-within { background: var(--sidebar-accent); color: var(--sidebar-accent-foreground); } .session-row:has(.session-main[data-active="true"]) { background: var(--sidebar-accent); box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--sidebar-ring) 22%, transparent); color: var(--sidebar-accent-foreground); } -.session-main { position: relative; width: 100%; min-height: 44px; display: grid; gap: 2px; padding: var(--space-2); border: 0; border-radius: var(--radius-md); background: transparent; color: inherit; cursor: pointer; text-align: left; text-decoration: none; transition: background-color 120ms ease-out, color 120ms ease-out, transform 120ms ease-out; } +.session-main { position: relative; width: 100%; min-height: 44px; display: grid; gap: 2px; padding: var(--space-2); border: 0; border-radius: 0; background: transparent; color: inherit; cursor: pointer; text-align: left; text-decoration: none; transition: color 120ms ease-out, transform 120ms ease-out; } .session-main[data-active="true"] { color: var(--sidebar-accent-foreground); background: transparent; } .session-main[data-active="true"]::before { position: absolute; border-radius: 3px; content: ""; top: var(--space-3); bottom: var(--space-3); left: 0; width: 2px; background: var(--sidebar-ring); } /* Read-only rows (the secondary pages) render no menu trigger, so the 44px @@ -852,10 +852,10 @@ button:disabled { cursor: default; opacity: .45; } .sidebar-group-label { display: flex; align-items: center; gap: 5px; margin: var(--space-2) 0 var(--space-1); color: var(--color-ink-tertiary); font-size: var(--type-overline); font-weight: 500; letter-spacing: .02em; } .sidebar-group-label > svg { flex: 0 0 auto; color: var(--color-action); } .session-list-sentinel { height: 1px; } -.session-menu-trigger { width: 44px; height: 44px; display: grid; place-items: center; justify-self: center; padding: 0; border: 0; border-radius: 0; background: transparent; color: inherit; cursor: pointer; opacity: .64; transition: background-color 120ms ease-out, color 120ms ease-out, opacity 120ms ease-out, transform 120ms ease-out; } +.session-menu-trigger { width: 44px; min-height: 44px; align-self: stretch; display: grid; place-items: center; justify-self: stretch; padding: 0; border: 0; border-radius: 0; background: transparent; color: inherit; cursor: pointer; opacity: .64; transition: color 120ms ease-out, opacity 120ms ease-out; } .session-menu-trigger > svg { width: 18px; height: 18px; } .session-row:hover .session-menu-trigger, .session-row:focus-within .session-menu-trigger, .session-menu-trigger[aria-expanded="true"] { opacity: 1; } -.session-menu-trigger:hover, .session-menu-trigger[aria-expanded="true"] { background: var(--color-canvas); color: var(--color-ink); } +.session-menu-trigger:hover, .session-menu-trigger[aria-expanded="true"] { background: transparent; color: inherit; } .session-actions-positioner { z-index: 30; max-width: calc(100vw - var(--space-6)); max-height: calc(100dvh - var(--space-6)); outline: 0; } .session-actions { min-width: 176px; max-height: var(--available-height); overflow-y: auto; display: grid; gap: var(--space-1); padding: var(--space-2); border: 1px solid var(--sidebar-border); border-radius: var(--radius-xl); background: var(--color-canvas); box-shadow: var(--shadow-elevated); transform-origin: var(--transform-origin); transition: opacity 120ms ease-out, transform 120ms var(--ease-out); } .session-actions[data-starting-style], .session-actions[data-ending-style] { opacity: 0; transform: translateY(var(--space-1)) scale(.98); } diff --git a/frontend/tests/sidebar-contract.test.ts b/frontend/tests/sidebar-contract.test.ts index f7a8b91e..525ca324 100644 --- a/frontend/tests/sidebar-contract.test.ts +++ b/frontend/tests/sidebar-contract.test.ts @@ -494,10 +494,18 @@ test("nests sidebar lists under one heading scale without an archive toggle", () test("renders each session title and menu as one unified row surface", () => { assert.match(cssBlock(".session-row"), /grid-template-columns:\s*minmax\(0,\s*1fr\)\s+44px/); + assert.match(cssBlock(".session-row"), /overflow:\s*hidden/); assert.match(globalStyles, /\.session-row:has\(\.session-main\[data-active="true"\]\)[^{]*\{[^}]*background:\s*var\(--sidebar-accent\)/); + assert.match(cssBlock(".session-main"), /border-radius:\s*0/); assert.match(cssBlock('.session-main[data-active="true"]'), /background:\s*transparent/); assert.match(cssBlock(".session-menu-trigger"), /border-radius:\s*0/); + assert.match(cssBlock(".session-menu-trigger"), /align-self:\s*stretch/); + assert.match(cssBlock(".session-menu-trigger"), /min-height:\s*44px/); assert.doesNotMatch(cssBlock(".session-menu-trigger"), /border-radius:\s*50%/); + assert.doesNotMatch(cssBlock(".session-menu-trigger"), /(? {