fix(chat): UI 修复—抽屉+新建项目可见性/标题迁移/折叠按钮不遮挡logo

This commit is contained in:
lhl
2026-08-28 15:33:45 +08:00
parent a225c84dc8
commit 8ff935122c
2 changed files with 202 additions and 5 deletions
+187
View File
@@ -0,0 +1,187 @@
# 聊天前端 UI 修复计划(3 项)
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement task-by-task.
**Goal:** 修复 3 个 UI 问题:(1) 项目管理抽屉内的「+新建项目」入口不可见 → 强化可见性与渲染鲁棒性;(2) 顶栏标题文本迁移到侧边栏品牌名下方;(3) 折叠侧边栏后折叠按钮遮挡 logo。
**Architecture:** 纯前端改动,仅涉及 `src/genesis/server/static/chat.html`HTML 结构 + 内嵌 CSS + 内嵌 JS)。无后端、无 schema、无新依赖。
**Tech Stack:** 原生 HTML/CSS/JS(内嵌于 chat.html,无构建步骤、无前端测试框架);校验用 `node --check` + 浏览器手动核对。
## 根因说明(问题 1
代码层 `renderDrawerList()`(启动与 `openProjectDrawer → loadDrawerForm` 末尾均调用)确实会渲染「+新建项目」首项,且无 CSS 隐藏它、所有 `pf-*` 字段元素齐全。因此「看不到」是**可发现性问题**:该项与普通项目项外观几乎一致、位于左侧窄列易被忽略;同时若将来 `loadDrawerForm` 抛错会跳过 `renderDrawerList`,导致整列空白。
修复策略:**保留设计已有入口、不另增按钮**(遵循用户「不用再新增」的偏好),仅 (a) 给该项专属样式使其醒目,(b) 加固 `openProjectDrawer` 保证列表必现。
---
## File Structure
- Modify: `src/genesis/server/static/chat.html`
- HTML:删除 header `<h1>`;侧边栏 `.brand-name` 增加副标题;抽屉「+新建项目」项加 `di-create` 类。
- CSS:新增 `.brand-sub``.drawer-item.di-create` 及其 hover;折叠态 `#sidebar-toggle` / `#session-list` 规则。
- JS`renderDrawerList` 首项加 `di-create` 类;`openProjectDrawer` 加固。
- Docs: 本计划文档。
---
### Task 1: 让「+新建项目」可见且必现
**Files:** `src/genesis/server/static/chat.html`
**Interfaces:** Consumes `renderDrawerList``openProjectDrawer``loadDrawerForm``guardDrawerDirty``drawerSelected`
**Produces:** 抽屉左侧顶部始终显示一个虚线蓝底、加粗的「+ 新建项目」项;即使 `loadDrawerForm` 异常也不影响该列表渲染。
- [ ] **Step 1: 写失败验收(手动)**
浏览器打开(scripts/serve.py --fake)→ 顶栏项目下拉 → 点「+ 项目管理(打开抽屉)」→ 抽屉左侧列表**顶部**应有一个明显区别于项目项的「+ 新建项目」项,点击进入空白新建表单。
- [ ] **Step 2: 实现 — renderDrawerList 首项加 di-create 类**
替换(656-658):
```javascript
let html = '<div class="drawer-item' + (drawerSelected === null ? ' active' : '') + '" data-name="">'
+ '<span class="di-new">+</span><span class="di-name">新建项目</span></div>';
```
为:
```javascript
let html = '<div class="drawer-item di-create' + (drawerSelected === null ? ' active' : '') + '" data-name="">'
+ '<span class="di-new"></span><span class="di-name">新建项目</span></div>';
```
- [ ] **Step 3: 实现 — openProjectDrawer 加固**
替换(589-593):
```javascript
function openProjectDrawer(projectName) {
drawerOpen = true;
toggleProjectSwitcher(false);
drawer.classList.add('open');
loadDrawerForm(projectName === undefined ? drawerSelected : projectName);
}
```
为:
```javascript
function openProjectDrawer(projectName) {
drawerOpen = true;
toggleProjectSwitcher(false);
drawer.classList.add('open');
const sel = (projectName === undefined || projectName === null) ? drawerSelected : projectName;
try { loadDrawerForm(sel); } catch (e) { console.error(e); }
renderDrawerList(); // 始终渲染左侧列表(含 +新建项目)
}
```
- [ ] **Step 4: 实现 — CSS 强化 di-create**
`.drawer-item .di-new { color: var(--blue); }` 之后插入:
```css
.drawer-item.di-create {
border: 1px dashed var(--blue-100); background: var(--blue-50);
color: var(--blue-dark); font-weight: 700; justify-content: center; gap: 4px;
}
.drawer-item.di-create:hover { background: var(--blue-100); border-color: var(--blue); }
```
- [ ] **Step 5: 校验**
Run: `node --check`(抽取 <script> 校验语法)
浏览器核对 Step 1 验收点。
- [ ] **Step 6: 提交**
```bash
git add src/genesis/server/static/chat.html
git commit -m 'fix(chat): 抽屉 +新建项目 入口强化可见性并加固渲染'
```
---
### Task 2: 标题文本迁移
**Files:** `src/genesis/server/static/chat.html`
**Interfaces:** 纯展示层。
**Produces:** header 不再显示「Genesis 概要设计书 Agent」;侧边栏 logo 右侧「Genesis」下方显示「概要设计书做成Agent」。
- [ ] **Step 1: 写失败验收(手动)**
顶栏左侧不再出现旧标题;侧边栏顶部 logo 下方出现两行文字「Genesis」与「概要设计书做成Agent」。
- [ ] **Step 2: 实现 — 删除 header h1**
删除(362):
```html
<h1>Genesis 概要设计书 Agent</h1>
```
- [ ] **Step 3: 实现 — 品牌名副标题**
替换(353):
```html
<div class="brand-name">Genesis</div>
```
为:
```html
<div class="brand-name">Genesis<span class="brand-sub">概要设计书做成Agent</span></div>
```
- [ ] **Step 4: 实现 — CSS .brand-sub**
`#sidebar.collapsed .brand-name { display: none; }` 之后插入:
```css
.brand-sub { display: block; font-size: 11px; font-weight: 600; color: var(--gray-500); margin-top: 2px; letter-spacing: 0; }
```
- [ ] **Step 5: 校验**
`node --check` + 浏览器核对 Step 1。
- [ ] **Step 6: 提交**
```bash
git add src/genesis/server/static/chat.html
git commit -m 'fix(chat): 标题文本从顶栏迁移至侧边栏品牌名下方'
```
---
### Task 3: 折叠按钮不再遮挡 logo
**Files:** `src/genesis/server/static/chat.html`
**Interfaces:** 纯 CSS。
**Produces:** 侧边栏折叠态下,折叠按钮移至左下角,与顶部居中的 logo 分离;会话列表底部留白避免压住末项。
- [ ] **Step 1: 写失败验收(手动)**
折叠侧边栏:顶部 logo 完整可见,不被折叠按钮遮挡;展开态外观不变。
- [ ] **Step 2: 实现 — 折叠态定位**
`#sidebar.collapsed { width: 56px; }` 之后插入:
```css
#sidebar.collapsed #sidebar-toggle { top: auto; bottom: 14px; right: auto; left: 14px; }
#sidebar.collapsed #session-list { padding-bottom: 52px; }
```
- [ ] **Step 3: 校验**
`node --check` + 浏览器核对 Step 1。
- [ ] **Step 4: 提交**
```bash
git add src/genesis/server/static/chat.html
git commit -m 'fix(chat): 折叠态将切换按钮移至左下角避免遮挡 logo'
```
---
## Self-Review
- 问题 1 保留设计已有「+新建项目」入口(不新增按钮),仅做可见性强化 + 渲染加固,符合用户偏好。
- 问题 2 删除 header h1 并在侧边栏品牌名加副标题;折叠态 `.brand-name{display:none}` 已自动隐藏副标题。
- 问题 3 仅改折叠态 CSS,展开态 `right:-14px; top:18px` 不变,logo 在左、按钮在右本不重叠。
- 三项均为展示层改动,不影响后端与既有交互逻辑;`node --check` 通过即语法安全。
+15 -5
View File
@@ -55,6 +55,8 @@
position: relative;
}
#sidebar.collapsed { width: 56px; }
#sidebar.collapsed #sidebar-toggle { top: auto; bottom: 14px; right: auto; left: 14px; }
#sidebar.collapsed #session-list { padding-bottom: 52px; }
.brand {
display: flex; align-items: center; gap: 12px;
padding: 16px 12px 12px 16px; margin: 0 8px 8px;
@@ -73,6 +75,7 @@
.brand:hover .brand-logo { transform: scale(1.1); }
.brand-name { font-size: 17px; font-weight: 900; letter-spacing: -.02em; line-height: 1.1; }
#sidebar.collapsed .brand-name { display: none; }
.brand-sub { display: block; font-size: 11px; font-weight: 600; color: var(--gray-500); margin-top: 2px; letter-spacing: 0; }
/* 折叠按钮 */
#sidebar-toggle {
@@ -312,6 +315,11 @@
.drawer-item.active { background: #fff; border-color: var(--blue-100); color: var(--blue-dark); font-weight: 700; box-shadow: var(--shadow-1); }
.drawer-item .di-name { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.drawer-item .di-new { color: var(--blue); }
.drawer-item.di-create {
border: 1px dashed var(--blue-100); background: var(--blue-50);
color: var(--blue-dark); font-weight: 700; justify-content: center; gap: 4px;
}
.drawer-item.di-create:hover { background: var(--blue-100); border-color: var(--blue); }
.drawer-form { flex: 1; overflow-y: auto; padding: 16px 20px; display: flex; flex-direction: column; gap: 10px; }
.drawer-form label { font-size: 11px; font-weight: 700; color: var(--gray-500); text-transform: uppercase; letter-spacing: .1em; margin-bottom: 2px; display: block; }
.drawer-form input {
@@ -350,7 +358,7 @@
<button id="sidebar-toggle" title="折叠/展开侧边栏">«</button>
<div class="brand">
<div class="brand-logo">G</div>
<div class="brand-name">Genesis</div>
<div class="brand-name">Genesis<span class="brand-sub">概要设计书做成Agent</span></div>
</div>
<button id="new-chat" title="新建会话"><span class="ic-plus">+</span><span class="nc-text">新会话</span></button>
<h2>会话历史</h2>
@@ -359,7 +367,7 @@
<div id="main">
<header>
<h1>Genesis 概要设计书 Agent</h1>
<div class="proj-switcher" id="proj-switcher">
<button class="ps-current" id="ps-current" type="button" title="切换项目">
<span class="ps-label">项目</span>
@@ -590,7 +598,9 @@ function openProjectDrawer(projectName) {
drawerOpen = true;
toggleProjectSwitcher(false);
drawer.classList.add('open');
loadDrawerForm(projectName === undefined ? drawerSelected : projectName);
const sel = (projectName === undefined || projectName === null) ? drawerSelected : projectName;
try { loadDrawerForm(sel); } catch (e) { console.error(e); }
renderDrawerList(); // 始终渲染左侧列表(含 +新建项目)
}
function closeProjectDrawer() {
@@ -654,8 +664,8 @@ function guardDrawerDirty() {
}
function renderDrawerList() {
let html = '<div class="drawer-item' + (drawerSelected === null ? ' active' : '') + '" data-name="">'
+ '<span class="di-new">+</span><span class="di-name">新建项目</span></div>';
let html = '<div class="drawer-item di-create' + (drawerSelected === null ? ' active' : '') + '" data-name="">'
+ '<span class="di-new"></span><span class="di-name">新建项目</span></div>';
for (const p of projects) {
html += '<div class="drawer-item' + (p.name === drawerSelected ? ' active' : '') + '" data-name="' + esc(p.name) + '">'
+ '<span class="di-name">' + esc(p.display_name || p.name) + '</span></div>';