diff --git a/docs/superpowers/plans/2026-09-12-drawer-favicon.md b/docs/superpowers/plans/2026-09-12-drawer-favicon.md new file mode 100644 index 0000000..ca85e87 --- /dev/null +++ b/docs/superpowers/plans/2026-09-12-drawer-favicon.md @@ -0,0 +1,67 @@ +# favicon 修复 + 抽屉(删左栏/宽度变量/蒙版)— 实施计划 + +目标文件:`src/genesis/server/{app.py}`、`src/genesis/server/static/{chat.html,chat.css,chat.js}`、新增 `static/favicon.svg` +后端改动仅 favicon 相关;其余为前端。 + +## 已确认决策 +- D1 抽屉宽度 `--drawer-w: 520px`。 +- D2 蒙版 `rgba(0,0,0,.5)`。 +- D3 点蒙版关闭抽屉(带未保存确认)。 +- D4 删除抽屉左侧项目列表,抽屉只留表单。 + +--- + +## Task 1:favicon 404 +- 新增 `src/genesis/server/static/favicon.svg`:品牌「G」(深底 `#0B0F17` + accent `#5EEAD4`)。 +- `chat.html
` 增:``。 +- `app.py`: + - `from fastapi.responses import FileResponse, HTMLResponse, Response`。 + - 在 `index` 附近新增 `@app.get("/favicon.ico")` → `Response(status_code=204)`。 + - `_asset_names` 加入 `"favicon.svg"`。 + +## Task 2:抽屉删左栏 + 宽度变量 + 蒙版 +- `chat.html`: + - 删除 ``。 + - 新增 ``(与 `#proj-drawer` 同级)。 +- `chat.js`: + - 删除 3 处 `renderDrawerList();`(`loadProjects`/`openProjectDrawer`/`loadDrawerForm`)与 `renderDrawerList()` 函数定义。 + - `openProjectDrawer` → mask `show`;`closeProjectDrawer` → 过 `guardDrawerDirty()` 后 mask 隐藏。 + - 点蒙版 → `closeProjectDrawer()`。 +- `chat.css`: + - `:root` 增 `--drawer-w: 520px; --mask-bg: rgba(0,0,0,.5);`;`#proj-drawer { width: var(--drawer-w); }`。 + - 删除 `.drawer-list` 规则(`#drawer-list` 不再存在)。 + - 新增 `.drawer-mask`(fixed/inset/z-index 99/opacity+visibility 过渡/`.show`)与 `@media (max-width:860px) .drawer-mask{display:none}`。 + +## Task 3:验证与提交 +- `node --check`、`node --test`、`python -m pytest`(≥99%)。 +- 手动:favicon 无 404;抽屉只留表单、宽 520px;蒙版 0.5;点蒙版/×/Esc 关闭(带未保存确认)。 +- 更新 `_AI_USAGE_LOG.md` 并提交。 + +## 风险 +- 蒙版使抽屉从"非模态"变"模态"(打开时点侧边栏会被蒙版拦截并关闭抽屉)。 +- 窄屏(≤860px)抽屉全屏,蒙版隐藏,仅 ×/Esc 关闭。 + +--- + +## 评审结论与计划调整(plan-eng-review) + +### 采纳的修正 +- **F5(真实缺陷,修正计划)**:**不要**把 `.drawer-body` 改成 `display:block`。保持 `flex:1; display:flex; min-height:0`,仅删除 `.drawer-list` 规则——否则 `.drawer-form { flex:1; overflow-y:auto }` 失去高度约束,长表单溢出且不滚动。 +- **F6(测试缺口,采纳)**:新增 favicon 回归测试(`tests/test_server_api.py`):`GET /favicon.ico` == 204 且 `GET /` 文本含 `rel="icon"`。 +- **窄屏蒙版**:保留 `@media (max-width:860px) .drawer-mask { display:none }`(全屏抽屉时仅 ×/Esc 关闭)。 + +### 记录(接受,不改) +- F3 行为变更:加蒙版后抽屉转为**模态**——打开时点侧边栏/空白会被蒙版拦截并触发关闭(未保存先确认)。 +- F4:蒙版不加 `aria-hidden`,作为纯装饰层。 +- F7/F8/F9:层叠、幂等、资源版本一致性均按计划成立。 + +### 受影响任务更新 +- Task 2:删除 `.drawer-list` 规则即可,`.drawer-body` **保持不变**。 +- Task 3:新增 favicon 回归测试。 + +## GSTACK REVIEW REPORT +- STATUS: DONE +- REVIEWED: docs/superpowers/plans/2026-09-12-drawer-favicon.md +- FINDINGS: F5 缺陷(已修正) / F6 测试缺口(已采纳) / F3 行为变更(记录) / F4 a11y(记录);F7-F9 接受 +- KEY DECISION: `.drawer-body` 保持 flex;新增 favicon 回归测试;窄屏隐藏蒙版 +- NEXT: 待用户批准后实施 diff --git a/src/genesis/server/app.py b/src/genesis/server/app.py index fe46cf8..89d0d56 100644 --- a/src/genesis/server/app.py +++ b/src/genesis/server/app.py @@ -16,7 +16,7 @@ from pathlib import Path from typing import Any from fastapi import FastAPI, File, Form, HTTPException, UploadFile, WebSocket, WebSocketDisconnect -from fastapi.responses import FileResponse, HTMLResponse +from fastapi.responses import FileResponse, HTMLResponse, Response from fastapi.staticfiles import StaticFiles from pydantic import BaseModel @@ -117,7 +117,7 @@ def create_app( static_dir = Path(__file__).parent / "static" # 静态资源版本号:按 mtime 计算,随任何前端文件变更而变化(重启后生效) - _asset_names = ("chat.html", "chat.css", "chat.js", "chat_state.js", "chat_ws.js") + _asset_names = ("chat.html", "chat.css", "chat.js", "chat_state.js", "chat_ws.js", "favicon.svg") try: _asset_ver = str(max(int((static_dir / n).stat().st_mtime_ns) for n in _asset_names if (static_dir / n).exists())) @@ -138,6 +138,11 @@ def create_app( def index(): return HTMLResponse(chat_html, headers={"Cache-Control": "no-store", "Pragma": "no-cache"}) + @app.get("/favicon.ico") + def favicon(): + # 浏览器默认请求;返回 204 避免 404(真实图标由 指向 favicon.svg) + return Response(status_code=204) + # 静态资源(chat.css / chat_state.js / chat_ws.js / 未来的 chat.js) # 统一通过 /static/