chore: rebuild release package
This commit is contained in:
44
.planning/sidebar-collapse-hover/findings.md
Normal file
44
.planning/sidebar-collapse-hover/findings.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 调研记录
|
||||
|
||||
## 用户需求
|
||||
|
||||
- 顶部标题旁放置左侧菜单收起/展开按钮。
|
||||
- 固定收起后仍保留一条窄侧边触发区。
|
||||
- 鼠标移入触发区时侧栏以悬浮层临时展开,鼠标移出后自动收回。
|
||||
- 先完成通用功能,再兼容现有主题。
|
||||
|
||||
## 视觉输入
|
||||
|
||||
- 参考截图:`sessions/_attachments/1c730846-9ff5-4b5e-a522-afdd2c5aa9e0.png`。
|
||||
- 截图显示标题位于主内容顶栏左侧,左侧菜单与主内容之间有明确竖向边界。
|
||||
- 本任务不新增主题和图片资产,截图仅用于按钮位置与侧栏层级参考。
|
||||
|
||||
## 工程基线
|
||||
|
||||
- `codebase-memory-mcp` 项目 `home-cc-web` 索引状态为 `ready`,节点 3883、边 8420。
|
||||
- 任务开始时 `git status --short` 无输出,工作树干净。
|
||||
- 主题技能要求共享功能与主题专属视觉分层,并在真实浏览器覆盖动态状态和多视口。
|
||||
- Trellis 开发者为 `shiyue`;当前 Trellis 任务仍是既有的 `07-17-gilded-wasteland-theme`,按工作流规则不覆盖当前任务指针。
|
||||
|
||||
## 初步代码定位
|
||||
|
||||
- `public/app.js` 已有 `openSidebar()` / `closeSidebar()` 和触屏滑动逻辑,现状服务于移动端 `.open` 状态。
|
||||
- `scripts/regression.js` 的现有主题契约包含 `gilded`、`wasteland` 等侧栏断言,新增共享交互需避免破坏这些移动端规则。
|
||||
- 无障碍要求:顶部按钮需提供可访问名称、`aria-expanded`、原生键盘激活与可见焦点;窄触发条不进入 Tab 顺序。
|
||||
- `public/index.html` 已有 `#menu-btn`,位置正好在 `#chat-title` 之前;无需新增第二个按钮。
|
||||
- `#menu-btn` 当前只在 `max-width: 768px` 显示并切换 `.sidebar.open`,桌面端可复用同一按钮切换新的 `.app.sidebar-collapsed` 固定状态。
|
||||
- 桌面收起可通过“完整宽度侧栏 + 负右外边距 + translateX”仅占用窄轨道宽度;hover 时只恢复 transform,因布局占位不变,不会推动正文。
|
||||
- 主题侧栏宽度统一来自 `--sidebar-width`,包括暗金荒野的 316px;共享几何规则应继续引用变量,主题只需补触发轨颜色/阴影。
|
||||
- 通用浅色主题直接继承各自的 `--bg-secondary` / `--border-color` / `--accent`;暗色主题统一提高悬浮层阴影,`gilded` 与 `wasteland` 分别补暖铜和暗金触发轨,避免共享轨道破坏主题质感。
|
||||
|
||||
## 浏览器证据
|
||||
|
||||
- Firefox 152(headless + geckodriver)在 1440 桌面精细指针下:展开侧栏宽 280px、正文起点 x=280;固定收起后侧栏范围 x=-266..14、正文起点 x=14;hover 后侧栏恢复 x=0..280,正文仍保持 x=14;移出与刷新后均恢复固定收起。
|
||||
- 按钮桌面状态从 `aria-expanded=true / 收起左侧菜单` 正确切到 `false / 固定展开左侧菜单`;缓存资源实际加载 `20260718-sidebar-collapse-1`。
|
||||
- `wasteland` 1672 视口截图确认 316px 暗金侧栏悬浮完整展开并覆盖正文而不推动正文;`washi` 收起/悬浮截图确认轨道和层级正常。
|
||||
- 500px 窄屏下沿用移动抽屉:关闭时侧栏位于视口外,打开后 `aria-expanded=true` 且遮罩显示,点击侧栏外坐标后关闭;横向溢出均为 0。
|
||||
- Firefox headless 普通窗口接口的内容区最小宽度为 500px,下一轮使用 WebDriver BiDi `setViewport` 补测精确 390px。
|
||||
- WebDriver BiDi 稳态验收覆盖全部 11 个现有主题:10 个主题侧栏宽 280px,`wasteland` 为 316px;每个主题收起右边界均为 x=14、hover 左边界均为 x=0、正文起点保持 x=14、横向溢出为 0。
|
||||
- 键盘 `:focus-within` 在 1440×900 下完整展开侧栏而不改变固定状态;精确 1672×941、500×844、390×844 均通过。
|
||||
- 宽屏无 hover / 无精细指针的真实浏览器边界:即使本地持久化值为收起,侧栏仍完整显示,按钮隐藏且 `aria-expanded=true`,视觉与无障碍状态一致。
|
||||
- 独立终审在媒体状态机修订后通过,未发现剩余中高风险问题。
|
||||
19
.planning/sidebar-collapse-hover/progress.md
Normal file
19
.planning/sidebar-collapse-hover/progress.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# 进度日志
|
||||
|
||||
- 2026-07-18:确认交互语义,读取仓库、Trellis、计划跟踪及主题架构规范。
|
||||
- 2026-07-18:确认代码索引可用且工作树干净,建立持久化计划。
|
||||
- 2026-07-18:独立计划审查补充无障碍要求后通过;完成侧栏、顶栏按钮、移动端抽屉和主题覆盖入口定位。
|
||||
- 2026-07-18:复用标题旁现有菜单按钮实现桌面固定状态、本地持久化、移动端原行为与动态无障碍状态;JS 语法检查通过。
|
||||
- 2026-07-18:加入 14px 窄触发轨、hover/focus-within 临时展开和 reduced-motion 降级;共享交互层置于全部主题样式之后。
|
||||
- 2026-07-18:补齐浅色/暗色语义适配、gilded 暖铜轨、wasteland 暗金轨,并更新 CSS/JS cache bust;移动端规则保持隔离。
|
||||
- 2026-07-18:新增 sidebar-collapse 专项回归目标,覆盖 DOM、无障碍、持久化、响应式分支、轨道交互、主题隔离与 cache bust;专项回归通过。
|
||||
- 2026-07-18:旧主题首轮回归暴露 cache-bust 断言未同步,已更新 gilded/wasteland 契约,待重跑。
|
||||
- 2026-07-18:第二轮回归补齐共享暗色选择器中的 gilded,再保留其后置暖色阴影覆盖。
|
||||
- 2026-07-18:第三轮统一验证通过:app/regression 语法、sidebar-collapse、gilded-theme、wasteland-theme、全量 regression、git diff --check。
|
||||
- 2026-07-18:Firefox headless 首轮因默认无精细指针而无法进入桌面交互分支,未产生产品侧失败结论;准备使用显式桌面指针能力重测。
|
||||
- 2026-07-18:Firefox 第二轮已执行主要桌面/主题/移动状态,最终遮罩关闭因 WebDriver 中心点落在侧栏下被拦截;改用侧栏外坐标重测。
|
||||
- 2026-07-18:Firefox 第三轮完成 washi 1440 展开/收起/hover/移出/刷新持久化、wasteland 1672 主题悬浮、gilded/wasteland 500 窄屏抽屉与遮罩关闭;截图已人工复核,横向溢出为 0。
|
||||
- 2026-07-18:独立终审要求收敛 JS/CSS 媒体状态机;已拆分移动抽屉与桌面可收起模式,并明确 aria-expanded 只表达固定/抽屉状态,临时预览不修改它。
|
||||
- 2026-07-18:媒体状态机修订后的首轮专项回归因旧函数名正则失败,已同步测试契约。
|
||||
- 2026-07-18:修订后再次通过语法、sidebar-collapse、gilded-theme、wasteland-theme、全量 regression 与 git diff --check。
|
||||
- 2026-07-18:Firefox BiDi 最终验收通过全部 11 个主题、键盘 focus、1672/1440/500/390 视口、移动遮罩关闭及 wide-no-hover 边界;独立终审通过,任务完成。
|
||||
37
.planning/sidebar-collapse-hover/task_plan.md
Normal file
37
.planning/sidebar-collapse-hover/task_plan.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 左侧菜单收起与悬浮展开
|
||||
|
||||
## 目标
|
||||
|
||||
在不改变现有会话业务逻辑的前提下,为桌面端左侧菜单增加固定收起/展开按钮;收起后保留窄触发条,鼠标移入临时悬浮展开,移出恢复收起,并兼容所有现有主题。
|
||||
|
||||
## 阶段
|
||||
|
||||
1. **COMPLETE** — 建立基线并定位侧栏、标题栏与主题入口
|
||||
2. **COMPLETE** — 实现固定收起状态与顶部标题旁按钮
|
||||
3. **COMPLETE** — 实现窄触发条和移入悬浮展开、移出收回
|
||||
4. **COMPLETE** — 补齐现有主题及桌面/窄屏样式兼容
|
||||
5. **COMPLETE** — 增加侧栏交互、无障碍与主题契约回归测试
|
||||
6. **COMPLETE** — 执行语法、专项回归和全量相关测试
|
||||
7. **COMPLETE** — 使用真实浏览器验收多主题与关键交互状态
|
||||
|
||||
## 关键决策
|
||||
|
||||
- 固定状态和悬浮预览状态分离,悬浮不修改用户固定选择。
|
||||
- 功能层使用共享 DOM/JS/CSS,主题层只覆盖语义变量或受主题 ID 限定的视觉声明。
|
||||
- 移动端沿用现有响应式侧栏逻辑,避免桌面悬浮规则破坏触屏操作。
|
||||
- 顶部按钮提供可访问名称、`aria-expanded`、键盘原生激活与可见焦点;悬浮触发条不进入 Tab 顺序,避免 hover-only 交互阻断键盘用户。
|
||||
|
||||
## 错误记录
|
||||
|
||||
- 第一次把共享侧栏交互层移动到 CSS 文件末尾时,补丁上下文误用了相邻的暗金主题选择器,未命中且未产生文件改动;随后按精确边界重新移动。
|
||||
- 首轮旧主题专项回归因 `gilded` / `wasteland` 契约仍断言旧 cache-bust URL 而失败;同步更新为本次版本后重跑。
|
||||
- 第二轮 `wasteland` 回归识别到新增共享暗色 `:is(...)` 未列出 `gilded`,违反既有“共享暗色选择器包含全部暗色主题”契约;已纳入后再由专属变量覆盖暖色阴影。
|
||||
- 首轮 Firefox headless 验收会话默认报告无精细指针,桌面按钮按媒体查询隐藏并导致 WebDriver 点击失败;改为在桌面验收会话显式模拟 `fine + hover` 指针能力。
|
||||
- 第二轮 Firefox 已完成主要状态测量,但 WebDriver 点击移动遮罩时选择了被侧栏覆盖的元素中心点并被拦截;改用侧栏外 `x=350` 的真实指针点击验证关闭。
|
||||
- 独立终审发现 JS 的移动输入判断与桌面收起 CSS 能力条件不完全一致,宽屏无 hover 设备可能出现视觉与 aria 状态不一致;拆分 drawer / desktop-collapse 媒体对象并与 CSS 对齐。
|
||||
- 状态机修订后首轮 sidebar-collapse 回归仍匹配旧 `isMobileInputMode()` 函数名而失败;断言同步为 drawer → desktop-collapse 两分支顺序后重跑。
|
||||
|
||||
## 完成结论
|
||||
|
||||
- 7 个阶段全部完成;独立终审复核通过。
|
||||
- 无新增主题、资产或远程运行时依赖;所有现有主题共用功能层,富视觉主题仅覆盖轨道视觉。
|
||||
10
.planning/wasteland-narrow-position-tuning/findings.md
Normal file
10
.planning/wasteland-narrow-position-tuning/findings.md
Normal file
@@ -0,0 +1,10 @@
|
||||
# 调研记录
|
||||
|
||||
- 当前 721–1100px 与 ≤720px 最终值均为 90% 50%。
|
||||
- 用户实机反馈 90% 仍“有点太靠左”,需要小幅降低百分比,让背景图向右回移。
|
||||
- 85% 位于已验收的 82% 与 90% 候选之间,属于小步微调,避免退回旧 70% 的偏右构图。
|
||||
- 两个窄屏区间已同步为 85% 50%,cache bust 更新为 `20260718-wasteland-bg-right-3`,回归契约同步锁定最终高保真层。
|
||||
- 用户在 85% 浏览器验收前明确指定“75% 试试”;跳过 85% 的无效视觉验收,两个窄屏区间直接改为 75% 50%,cache bust 推进到 `20260718-wasteland-bg-right-4`。
|
||||
- Firefox 实际 CSS 验收:1101px 保持桌面居中;1100/1022/721/720/590/390px 均命中 75% 50%,断点连续且横向溢出为 0。
|
||||
- 75% 相比 90% 让骑士明显向右回移;390px 主体位于中右区域,590px 法杖圆环更接近右侧裁切线,符合本轮明确的向右调整要求。
|
||||
- 独立终审确认 75% 最终层叠、cache right-4、回归契约与上一轮侧栏功能均无阻断问题。
|
||||
11
.planning/wasteland-narrow-position-tuning/progress.md
Normal file
11
.planning/wasteland-narrow-position-tuning/progress.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 进度日志
|
||||
|
||||
- 2026-07-18:收到 90% 仍偏左的反馈,建立 85% 微调计划。
|
||||
- 2026-07-18:独立计划审查批准;确认两个窄屏区间、回归和 cache bust 当前均为 90% / right-2。
|
||||
- 2026-07-18:实现 90%→85%,同步两个窄屏区间、cache right-3 与全部相关回归断言。
|
||||
- 2026-07-18:用户指定改试 75%;在 85% 浏览器验收前直接切换到 75%,同步 cache right-4 与回归。
|
||||
- 2026-07-18:75% 版本 wasteland/sidebar 专项与 diff check 通过,进入真实浏览器与全量回归。
|
||||
- 2026-07-18:Firefox BiDi 完成 1101/1100/1022/721/720/590/390px 验收,computed position 为预期 75%,截图人工复核通过。
|
||||
- 2026-07-18:首轮最终全量回归断言通过但在 60 秒边界退出 124,进入拆分重跑。
|
||||
- 2026-07-18:拆分重跑后全量回归和 diff check 正常退出 0;工程与浏览器验收完成。
|
||||
- 2026-07-18:75% 最终独立复审通过,任务完成。
|
||||
27
.planning/wasteland-narrow-position-tuning/task_plan.md
Normal file
27
.planning/wasteland-narrow-position-tuning/task_plan.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# 暗金窄屏背景位置二次微调
|
||||
|
||||
## 目标
|
||||
|
||||
根据用户最新反馈把暗金荒野窄屏背景最终调整为 75%,让骑士进一步向右移动;同步两个窄屏区间、缓存与回归。
|
||||
|
||||
## 阶段
|
||||
|
||||
1. **COMPLETE** — 确认用户反馈与当前 90% 基线
|
||||
2. **COMPLETE** — 调整为 75% 并同步缓存与回归契约
|
||||
3. **COMPLETE** — 执行专项全量测试与多窄屏浏览器验收
|
||||
4. **COMPLETE** — 完成独立复审并清理临时跟踪文件
|
||||
|
||||
## 完成结论
|
||||
|
||||
- 最终采用用户指定的 75% 50%。
|
||||
- 两个窄屏区间、缓存、回归、浏览器验收和独立复审均已完成。
|
||||
|
||||
## 范围
|
||||
|
||||
- 只改 `wasteland` 高保真窄屏 `body background-position`、cache bust 和对应回归。
|
||||
- 不修改背景资产、DOM、消息布局、侧栏功能或其他主题。
|
||||
|
||||
## 错误记录
|
||||
|
||||
- 同步计划文案时首次补丁未包含 CSV 的实际时间戳列而未命中;源码未受影响,随后按完整行精确更新。
|
||||
- 首轮最终全量回归已输出 `Regression checks passed`,但进程在 60 秒边界退出 124,后续 diff check 未执行;拆开命令后重跑。
|
||||
44
.planning/wasteland-narrow-right-focus/findings.md
Normal file
44
.planning/wasteland-narrow-right-focus/findings.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 调研记录
|
||||
|
||||
## 用户输入
|
||||
|
||||
- 目标主题:`wasteland`(暗金荒野)。
|
||||
- 目标状态:窄屏。
|
||||
- 目标方向:优先显示背景图右侧,而不是当前偏左裁切。
|
||||
- 参考截图:`sessions/_attachments/ebfd9a2e-e73e-425a-87da-fe79b17964ec.png`、`sessions/_attachments/c8ab2b98-5543-4a99-8cf1-b4e14eafff45.png`。
|
||||
|
||||
## 资产角色
|
||||
|
||||
- 现有运行时背景:`public/assets/themes/wasteland/background.webp`,保持原文件不变。
|
||||
- 两张会话附件仅作视觉对照,不进入 `public/`,本任务无派生资产。
|
||||
|
||||
## 代码与视觉基线
|
||||
|
||||
- 背景源为 1672×941 WebP,SHA-256:`ee52b136294a7a31181b67e209253fefe7ace8c2b76885e45621484fecb802cb`。
|
||||
- 最终高保真组件层在 `public/style.css` 文件后部再次声明 `html[data-theme='wasteland'] body`,因此早期同名规则不是最终修改点。
|
||||
- 当前只有 `max-width: 720px` 使用 `background-position: center, center, 70% 50%`;约 1022px 的用户截图未命中该断点,仍使用桌面 `center`,导致可见区域偏向源图中部。
|
||||
- 第一张截图表现为右侧窄条只露出骑士局部;第二张截图是目标构图:可见区域覆盖源图右半部,骑士落在可见区域左侧,右侧圆环与留白同时保留。
|
||||
- 初步判断应优先把既有 70% 右侧聚焦扩展到更宽的窄屏区间,而不是修改背景资产或消息层。
|
||||
- 现有 `wasteland-theme` 回归在 `scripts/regression.js` 中明确断言 720px / 70%,新增断点或调整数值必须同步契约。
|
||||
- 工作树已有上一轮侧栏功能的 `app.js` / `index.html` / `style.css` / `regression.js` 未提交改动;本任务只追加背景定位相关最小差异。
|
||||
|
||||
## 候选裁切对比(590×767)
|
||||
|
||||
- 50%:骑士大部分被裁到右侧边缘,左侧暗空区过多,不符合“显示右边”。
|
||||
- 70%:骑士主体进入画面,但仍偏右,右侧圆环与法杖显示不足。
|
||||
- 82%:骑士与圆环都进入画面,构图开始接近第二张参考图。
|
||||
- 90%:源图右侧区域更完整,骑士向左移动、右侧圆环基本完整,方向符合需求。
|
||||
- 下一步补测 100%(`right center`),在 90% 与 100% 中选择不会让骑士过度贴左的最终值。
|
||||
- 子代理核对确认:最终桌面规则位于高保真层,721–1100px 当前无专属定位;现有回归可能匹配早期旧规则产生假阳性,必须收窄到高保真层最终规则。
|
||||
- 100% / `right center` 最接近第二张参考图:骑士进入左半,法杖与右侧圆环完整保留,右侧区域表达最明确;选为最终值。
|
||||
- 断点策略:新增 721–1100px 右侧聚焦以覆盖用户约 1022px 的截图,同时把现有 ≤720px 最终规则从 70% 收敛为 `right center`,保证窄屏连续。
|
||||
- 用户实机反馈 `right center` 让主体“有点太靠左”;最终回调为 90% 50%,在原 70% 与 100% 之间保留右侧圆环,同时让骑士回到更自然的位置。
|
||||
|
||||
## 最终浏览器验收
|
||||
|
||||
- Firefox 152 + WebDriver BiDi 使用实际 CSS(无内联覆盖)验证:1101px 保持桌面 `50% 50%`;1100、1022、721、720、590、390px 均为第三层 `90% 50%`。
|
||||
- 721→720 两侧 computed position 一致,没有断点跳位;所有视口横向溢出为 0。
|
||||
- 1022×844:骑士位于中右区域,右侧圆环完整进入画面;590×767:骑士相较 100% 明显向右回移;390×844:主体靠左但未贴边,右侧法杖与圆环仍保留。
|
||||
- 浏览器实际加载 `style.css?v=20260718-wasteland-bg-right-2` / `app.js?v=20260718-wasteland-bg-right-2`,CSS 与背景 WebP 请求均为 HTTP 200。
|
||||
- 本任务未创建新主题、未改 DOM、未改资产;验收清单中的新主题注册、派生资产、动态组件状态均不适用,相关既有主题专项与全量回归用于确认未回归。
|
||||
- 独立终审确认最终层叠、主题隔离、cache bust、回归契约和上一轮侧栏功能均无阻断问题。
|
||||
13
.planning/wasteland-narrow-right-focus/progress.md
Normal file
13
.planning/wasteland-narrow-right-focus/progress.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# 进度日志
|
||||
|
||||
- 2026-07-18:确认只调整暗金主题窄屏背景裁切,建立持久化计划。
|
||||
- 2026-07-18:计划经独立审查批准;本地定位最终 720px / 70% 规则与回归断言,确认背景源尺寸/hash,开始浏览器候选对比。
|
||||
- 2026-07-18:Firefox BiDi 在 590×767 对比 50/70/82/90%,90% 最符合右侧聚焦方向;继续补测 100% 后定稿。
|
||||
- 2026-07-18:补测 100% 后确认 `right center` 最接近第二张参考图,进入主题隔离实现。
|
||||
- 2026-07-18:在高保真主题层新增 721–1100px 右侧聚焦,并把 ≤720px 最终规则改为 `right center`;CSS/JS cache bust 更新为 `20260718-wasteland-bg-right-1`。
|
||||
- 2026-07-18:回归收窄到最终高保真层,锁定两个窄屏区间的 `right center` 与新 cache bust;wasteland/sidebar/gilded 三个专项通过。
|
||||
- 2026-07-18:首轮全量回归因 60 秒超时退出 124,无断言失败输出,进入环境诊断与替代入口重跑。
|
||||
- 2026-07-18:直接 Node 入口全量回归在 60 秒内通过;语法、三个专项、全量回归与 diff check 均通过。
|
||||
- 2026-07-18:根据用户实机反馈将 100% 回调到 90%,同步两个窄屏区间、回归断言与 cache bust `20260718-wasteland-bg-right-2`,重新进入验证。
|
||||
- 2026-07-18:最终 Firefox 验收覆盖 1101/1100/1022/721/720/590/390px,90% 右侧聚焦连续且无溢出;最终全量回归再次通过,等待独立复审。
|
||||
- 2026-07-18:独立复审通过;任务完成,准备清理临时 TODO CSV。
|
||||
30
.planning/wasteland-narrow-right-focus/task_plan.md
Normal file
30
.planning/wasteland-narrow-right-focus/task_plan.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 暗金主题窄屏背景右侧聚焦
|
||||
|
||||
## 目标
|
||||
|
||||
仅调整 `wasteland` 暗金荒野主题在窄屏下的背景图横向裁切,让源图右侧区域优先进入视口,达到用户第二张参考图的构图倾向;不改消息、侧栏或其他主题。
|
||||
|
||||
## 阶段
|
||||
|
||||
1. **COMPLETE** — 建立脏工作树基线并定位窄屏背景规则
|
||||
2. **COMPLETE** — 用真实浏览器对比候选背景定位值
|
||||
3. **COMPLETE** — 实现暗金主题窄屏右侧聚焦并更新缓存版本
|
||||
4. **COMPLETE** — 补充窄屏背景定位回归契约
|
||||
5. **COMPLETE** — 执行主题专项、全量回归与静态检查
|
||||
6. **COMPLETE** — 完成多窄屏真实浏览器视觉验收与独立复审
|
||||
|
||||
## 范围边界
|
||||
|
||||
- 只修改 `html[data-theme='wasteland'] body` 的窄屏 `background-position` 及对应 cache bust / 回归。
|
||||
- 参考截图只用于构图对照,不作为运行时资产;不复制、不裁切、不修改现有背景源图。
|
||||
- 保留当前工作树中上一轮侧栏功能的未提交改动,不覆盖或重排无关区块。
|
||||
|
||||
## 完成结论
|
||||
|
||||
- 用户反馈 100% 太靠左后,最终采用 90% 50%。
|
||||
- 6 个阶段全部完成,独立终审通过;未新增或修改背景资产。
|
||||
|
||||
## 错误记录
|
||||
|
||||
- `codebase-memory-mcp` 本轮调用返回 `Transport closed`;按仓库降级规则使用本地 `rg` / `sed` 完成定位。
|
||||
- 首轮 `npm run regression` 在 60 秒门禁处退出 124,专项均已通过且未输出断言失败;检查残留进程后改用直接 Node 入口重跑。
|
||||
Binary file not shown.
@@ -9,6 +9,9 @@
|
||||
const DIVIDER_TIME_STORAGE_KEY = 'cc-web-show-divider-time';
|
||||
const CCWEB_PROMPT_VIEW_MODE_STORAGE_KEY = 'cc-web-ccweb-prompt-view-mode';
|
||||
const PROJECT_COLLAPSE_STORAGE_KEY = 'cc-web-collapsed-projects';
|
||||
const SIDEBAR_COLLAPSE_STORAGE_KEY = 'cc-web-sidebar-collapsed';
|
||||
const SIDEBAR_DRAWER_MEDIA_QUERY = '(max-width: 768px)';
|
||||
const SIDEBAR_DESKTOP_COLLAPSE_MEDIA_QUERY = '(width > 768px) and (hover: hover) and (pointer: fine)';
|
||||
const CROSS_CONVERSATION_REPLY_COLLAPSE_STORAGE_KEY = 'cc-web-collapsed-cross-replies';
|
||||
const CROSS_CONVERSATION_REPLY_COLLAPSE_LIMIT = 500;
|
||||
const ASSISTANT_LAST_SECTION_BUTTON_CLASS = 'msg-last-section-btn';
|
||||
@@ -278,6 +281,9 @@
|
||||
const sidebar = $('#sidebar');
|
||||
const sidebarOverlay = $('#sidebar-overlay');
|
||||
const menuBtn = $('#menu-btn');
|
||||
const mobileInputMedia = window.matchMedia('(max-width: 768px), (pointer: coarse)');
|
||||
const sidebarDrawerMedia = window.matchMedia(SIDEBAR_DRAWER_MEDIA_QUERY);
|
||||
const sidebarDesktopCollapseMedia = window.matchMedia(SIDEBAR_DESKTOP_COLLAPSE_MEDIA_QUERY);
|
||||
const chatMain = document.querySelector('.chat-main');
|
||||
const newChatSplit = sidebar.querySelector('.new-chat-split');
|
||||
const newChatBtn = $('#new-chat-btn');
|
||||
@@ -8510,17 +8516,67 @@
|
||||
}
|
||||
|
||||
// --- Sidebar ---
|
||||
function readSidebarCollapsedPreference() {
|
||||
try {
|
||||
return localStorage.getItem(SIDEBAR_COLLAPSE_STORAGE_KEY) === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function persistSidebarCollapsedPreference(collapsed) {
|
||||
try {
|
||||
localStorage.setItem(SIDEBAR_COLLAPSE_STORAGE_KEY, collapsed ? 'true' : 'false');
|
||||
} catch {
|
||||
// 存储不可用时仍保留当前页面内的交互状态。
|
||||
}
|
||||
}
|
||||
|
||||
function syncSidebarButtonState() {
|
||||
const isDrawer = isSidebarDrawerMode();
|
||||
const isDesktopCollapsible = isSidebarDesktopCollapseMode();
|
||||
// aria-expanded 表示按钮控制的固定/抽屉状态;hover/focus 预览不改变用户的固定选择。
|
||||
const expanded = isDrawer
|
||||
? sidebar.classList.contains('open')
|
||||
: (isDesktopCollapsible ? !app.classList.contains('sidebar-collapsed') : true);
|
||||
const label = isDrawer
|
||||
? (expanded ? '关闭左侧菜单' : '打开左侧菜单')
|
||||
: (isDesktopCollapsible
|
||||
? (expanded ? '收起左侧菜单' : '固定展开左侧菜单')
|
||||
: '左侧菜单');
|
||||
menuBtn.setAttribute('aria-expanded', String(expanded));
|
||||
menuBtn.setAttribute('aria-label', label);
|
||||
menuBtn.title = label;
|
||||
}
|
||||
|
||||
function setSidebarCollapsed(collapsed, { persist = false } = {}) {
|
||||
const nextCollapsed = Boolean(collapsed);
|
||||
app.classList.toggle('sidebar-collapsed', nextCollapsed);
|
||||
if (persist) persistSidebarCollapsedPreference(nextCollapsed);
|
||||
syncSidebarButtonState();
|
||||
}
|
||||
|
||||
function openSidebar() {
|
||||
sidebar.classList.add('open');
|
||||
sidebarOverlay.hidden = false;
|
||||
syncSidebarButtonState();
|
||||
}
|
||||
function closeSidebar() {
|
||||
sidebar.classList.remove('open');
|
||||
sidebarOverlay.hidden = true;
|
||||
syncSidebarButtonState();
|
||||
}
|
||||
|
||||
function handleSidebarInputModeChange() {
|
||||
sidebar.classList.remove('open');
|
||||
sidebarOverlay.hidden = true;
|
||||
syncSidebarButtonState();
|
||||
}
|
||||
|
||||
setSidebarCollapsed(readSidebarCollapsedPreference());
|
||||
|
||||
function canOpenSidebarBySwipe(target) {
|
||||
if (!window.matchMedia('(max-width: 768px), (pointer: coarse)').matches) return false;
|
||||
if (!isSidebarDrawerMode()) return false;
|
||||
if (sidebar.classList.contains('open')) return false;
|
||||
if (sessionLoadingOverlay && !sessionLoadingOverlay.hidden) return false;
|
||||
if (!chatMain || !target || !chatMain.contains(target)) return false;
|
||||
@@ -8531,7 +8587,7 @@
|
||||
}
|
||||
|
||||
function canCloseSidebarBySwipe(target) {
|
||||
if (!window.matchMedia('(max-width: 768px), (pointer: coarse)').matches) return false;
|
||||
if (!isSidebarDrawerMode()) return false;
|
||||
if (!sidebar.classList.contains('open')) return false;
|
||||
if (!target) return false;
|
||||
return sidebar.contains(target) || target === sidebarOverlay;
|
||||
@@ -8996,7 +9052,15 @@
|
||||
}
|
||||
|
||||
function isMobileInputMode() {
|
||||
return window.matchMedia('(max-width: 768px), (pointer: coarse)').matches;
|
||||
return mobileInputMedia.matches;
|
||||
}
|
||||
|
||||
function isSidebarDrawerMode() {
|
||||
return sidebarDrawerMedia.matches;
|
||||
}
|
||||
|
||||
function isSidebarDesktopCollapseMode() {
|
||||
return sidebarDesktopCollapseMedia.matches;
|
||||
}
|
||||
|
||||
// --- Event Listeners ---
|
||||
@@ -9018,10 +9082,23 @@
|
||||
});
|
||||
|
||||
menuBtn.addEventListener('click', () => {
|
||||
sidebar.classList.contains('open') ? closeSidebar() : openSidebar();
|
||||
if (isSidebarDrawerMode()) {
|
||||
sidebar.classList.contains('open') ? closeSidebar() : openSidebar();
|
||||
return;
|
||||
}
|
||||
if (isSidebarDesktopCollapseMode()) {
|
||||
setSidebarCollapsed(!app.classList.contains('sidebar-collapsed'), { persist: true });
|
||||
}
|
||||
});
|
||||
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
[sidebarDrawerMedia, sidebarDesktopCollapseMedia].forEach((media) => {
|
||||
if (typeof media.addEventListener === 'function') {
|
||||
media.addEventListener('change', handleSidebarInputModeChange);
|
||||
} else if (typeof media.addListener === 'function') {
|
||||
media.addListener(handleSidebarInputModeChange);
|
||||
}
|
||||
});
|
||||
document.addEventListener('touchstart', handleSidebarSwipeStart, { passive: true });
|
||||
document.addEventListener('touchmove', handleSidebarSwipeMove, { passive: false });
|
||||
document.addEventListener('touchend', handleSidebarSwipeEnd, { passive: true });
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
document.documentElement.dataset.dividerTime = dividerTime;
|
||||
})();
|
||||
</script>
|
||||
<link rel="stylesheet" href="style.css?v=20260717-wasteland-hifi-23">
|
||||
<link rel="stylesheet" href="style.css?v=20260718-wasteland-bg-right-4">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/atom-one-dark.min.css">
|
||||
</head>
|
||||
<body>
|
||||
@@ -74,7 +74,7 @@
|
||||
<!-- Chat -->
|
||||
<main class="chat-main">
|
||||
<header class="chat-header">
|
||||
<button id="menu-btn" class="menu-btn" title="菜单">☰</button>
|
||||
<button id="menu-btn" class="menu-btn" type="button" title="收起左侧菜单" aria-label="收起左侧菜单" aria-controls="sidebar" aria-expanded="true">☰</button>
|
||||
<span id="chat-title" class="chat-title">新会话</span>
|
||||
<button id="chat-session-id-btn" class="chat-session-id-btn" type="button" title="复制当前会话 ID" hidden>ID</button>
|
||||
<button id="chat-cwd" class="chat-cwd" type="button" hidden></button>
|
||||
@@ -183,6 +183,6 @@
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.1/mermaid.min.js"></script>
|
||||
<script src="app.js?v=20260718-theme-bundle-4"></script>
|
||||
<script src="app.js?v=20260718-wasteland-bg-right-4"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
110
public/style.css
110
public/style.css
@@ -8888,6 +8888,12 @@ html[data-theme='wasteland'] .mode-select {
|
||||
background-position: right 10px center;
|
||||
}
|
||||
|
||||
@media (min-width: 721px) and (max-width: 1100px) {
|
||||
html[data-theme='wasteland'] body {
|
||||
background-position: center, center, 75% 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 720px) {
|
||||
html[data-theme='wasteland'] {
|
||||
--sidebar-width: min(88vw, 320px);
|
||||
@@ -8899,7 +8905,7 @@ html[data-theme='wasteland'] .mode-select {
|
||||
linear-gradient(90deg, rgba(3, 5, 5, 0.82), rgba(3, 5, 5, 0.46)),
|
||||
linear-gradient(180deg, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.48)),
|
||||
url('assets/themes/wasteland/background.webp');
|
||||
background-position: center, center, 70% 50%;
|
||||
background-position: center, center, 75% 50%;
|
||||
}
|
||||
|
||||
html[data-theme='wasteland'] .input-area {
|
||||
@@ -8967,3 +8973,105 @@ html[data-theme='wasteland'] :is(
|
||||
-webkit-backdrop-filter: blur(3px) saturate(1.16) brightness(1.28);
|
||||
backdrop-filter: blur(3px) saturate(1.16) brightness(1.28);
|
||||
}
|
||||
|
||||
/* === 桌面侧栏:固定收起与悬浮预览 === */
|
||||
:root {
|
||||
--sidebar-rail-width: 14px;
|
||||
--sidebar-hover-shadow: 18px 0 40px rgba(45, 31, 20, 0.16);
|
||||
}
|
||||
|
||||
:is(
|
||||
html[data-theme='carbon'],
|
||||
html[data-theme='nocturne'],
|
||||
html[data-theme='cinder'],
|
||||
html[data-theme='gilded'],
|
||||
html[data-theme='wasteland']
|
||||
) {
|
||||
--sidebar-hover-shadow: 22px 0 52px rgba(0, 0, 0, 0.48);
|
||||
}
|
||||
|
||||
html[data-theme='gilded'] {
|
||||
--sidebar-hover-shadow: 20px 0 46px rgba(69, 48, 25, 0.22);
|
||||
}
|
||||
|
||||
.menu-btn:focus-visible {
|
||||
outline: 2px solid var(--accent);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
@media (width > 768px) and (hover: hover) and (pointer: fine) {
|
||||
.menu-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
flex: 0 0 32px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.app.sidebar-collapsed .sidebar {
|
||||
position: relative;
|
||||
z-index: 90;
|
||||
width: var(--sidebar-width);
|
||||
min-width: var(--sidebar-width);
|
||||
margin-right: calc(var(--sidebar-rail-width) - var(--sidebar-width));
|
||||
transform: translateX(calc(var(--sidebar-rail-width) - var(--sidebar-width)));
|
||||
box-shadow: none;
|
||||
will-change: transform;
|
||||
transition:
|
||||
transform 180ms ease,
|
||||
margin-right 180ms ease,
|
||||
box-shadow 180ms ease;
|
||||
}
|
||||
|
||||
.app.sidebar-collapsed .sidebar:is(:hover, :focus-within) {
|
||||
transform: translateX(0);
|
||||
box-shadow: var(--sidebar-hover-shadow);
|
||||
}
|
||||
|
||||
.app.sidebar-collapsed .sidebar::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 3;
|
||||
width: var(--sidebar-rail-width);
|
||||
pointer-events: none;
|
||||
border-left: 1px solid var(--border-color);
|
||||
background:
|
||||
linear-gradient(90deg, transparent, color-mix(in srgb, var(--accent) 12%, transparent)),
|
||||
var(--bg-secondary);
|
||||
box-shadow: inset -1px 0 var(--border-color);
|
||||
opacity: 1;
|
||||
transition: opacity 120ms ease;
|
||||
}
|
||||
|
||||
.app.sidebar-collapsed .sidebar:is(:hover, :focus-within)::after {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
html[data-theme='gilded'] .app.sidebar-collapsed .sidebar::after {
|
||||
border-left-color: rgba(138, 97, 37, 0.34);
|
||||
background:
|
||||
linear-gradient(90deg, rgba(255, 247, 234, 0.42), rgba(168, 79, 37, 0.18)),
|
||||
#efe4d2;
|
||||
box-shadow: inset -1px 0 rgba(122, 63, 32, 0.42);
|
||||
}
|
||||
|
||||
html[data-theme='wasteland'] .app.sidebar-collapsed .sidebar::after {
|
||||
border-left-color: rgba(185, 139, 75, 0.38);
|
||||
background:
|
||||
linear-gradient(90deg, rgba(5, 7, 7, 0.82), rgba(196, 154, 90, 0.2)),
|
||||
#080a09;
|
||||
box-shadow: inset -1px 0 rgba(223, 180, 108, 0.32);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.app.sidebar-collapsed .sidebar,
|
||||
.app.sidebar-collapsed .sidebar::after {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -524,6 +524,69 @@ function createFakeCodexConfig(homeDir, { model = 'gpt-5.5', reasoningEffort = '
|
||||
].join('\n'));
|
||||
}
|
||||
|
||||
function assertFrontendSidebarCollapseContract() {
|
||||
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const indexSource = fs.readFileSync(PUBLIC_INDEX_PATH, 'utf8');
|
||||
const styleSource = fs.readFileSync(PUBLIC_STYLE_PATH, 'utf8');
|
||||
const buttonMarkup = indexSource.match(/<button id="menu-btn"[^>]*>/)?.[0] || '';
|
||||
|
||||
assert(buttonMarkup, 'Chat header should retain the sidebar toggle next to the title');
|
||||
['type="button"', 'aria-controls="sidebar"', 'aria-expanded="true"', 'aria-label="收起左侧菜单"'].forEach((attribute) => {
|
||||
assert(buttonMarkup.includes(attribute), `Sidebar toggle should expose ${attribute}`);
|
||||
});
|
||||
assert(source.includes("const SIDEBAR_COLLAPSE_STORAGE_KEY = 'cc-web-sidebar-collapsed'"), 'Desktop sidebar preference should use a stable storage key');
|
||||
assert(source.includes("const SIDEBAR_DRAWER_MEDIA_QUERY = '(max-width: 768px)'"), 'Sidebar drawer mode should match the mobile CSS breakpoint');
|
||||
assert(source.includes("const SIDEBAR_DESKTOP_COLLAPSE_MEDIA_QUERY = '(width > 768px) and (hover: hover) and (pointer: fine)'"), 'Desktop collapse mode should match the hover rail CSS capabilities');
|
||||
assert(source.includes("app.classList.toggle('sidebar-collapsed', nextCollapsed)"), 'Desktop sidebar should keep its fixed state on the app root');
|
||||
assert(source.includes('persistSidebarCollapsedPreference(nextCollapsed)'), 'Desktop sidebar toggle should persist the user preference');
|
||||
assert(
|
||||
source.includes("menuBtn.setAttribute('aria-expanded', String(expanded))")
|
||||
&& source.includes("menuBtn.setAttribute('aria-label', label)"),
|
||||
'Sidebar toggle should synchronize its accessible state and label'
|
||||
);
|
||||
assert(
|
||||
/menuBtn\.addEventListener\('click',[\s\S]*?if \(isSidebarDrawerMode\(\)\)[\s\S]*?openSidebar\(\)[\s\S]*?if \(isSidebarDesktopCollapseMode\(\)\)[\s\S]*?setSidebarCollapsed\(/.test(source),
|
||||
'Sidebar toggle should preserve the mobile drawer path before using desktop fixed collapse'
|
||||
);
|
||||
assert(
|
||||
source.includes('[sidebarDrawerMedia, sidebarDesktopCollapseMedia].forEach((media) =>')
|
||||
&& source.includes("media.addEventListener('change', handleSidebarInputModeChange)"),
|
||||
'Sidebar should reset transient state when either drawer or desktop collapse mode changes'
|
||||
);
|
||||
|
||||
assert(styleSource.includes('--sidebar-rail-width: 14px'), 'Collapsed desktop sidebar should retain a narrow hover rail');
|
||||
assert(
|
||||
styleSource.includes('@media (width > 768px) and (hover: hover) and (pointer: fine)'),
|
||||
'Desktop hover behavior should stay isolated from touch and narrow-screen drawers'
|
||||
);
|
||||
assert(
|
||||
styleSource.includes('margin-right: calc(var(--sidebar-rail-width) - var(--sidebar-width))')
|
||||
&& styleSource.includes('transform: translateX(calc(var(--sidebar-rail-width) - var(--sidebar-width)))'),
|
||||
'Collapsed sidebar should only consume the rail width without resizing theme-specific sidebars'
|
||||
);
|
||||
assert(
|
||||
styleSource.includes('.app.sidebar-collapsed .sidebar:is(:hover, :focus-within)')
|
||||
&& styleSource.includes('transform: translateX(0)'),
|
||||
'Collapsed sidebar should temporarily expand for pointer hover and keyboard focus'
|
||||
);
|
||||
assert(styleSource.includes('.menu-btn:focus-visible'), 'Sidebar toggle should retain a visible keyboard focus treatment');
|
||||
assert(
|
||||
source.includes('if (isSidebarDesktopCollapseMode())')
|
||||
&& source.includes("const expanded = isDrawer\n ? sidebar.classList.contains('open')\n : (isDesktopCollapsible ? !app.classList.contains('sidebar-collapsed') : true)"),
|
||||
'Unsupported wide-screen pointer modes should stay visually and accessibly expanded'
|
||||
);
|
||||
assert(
|
||||
styleSource.includes("html[data-theme='gilded'] .app.sidebar-collapsed .sidebar::after")
|
||||
&& styleSource.includes("html[data-theme='wasteland'] .app.sidebar-collapsed .sidebar::after"),
|
||||
'Rich themes should provide isolated rail treatments on top of the shared semantic fallback'
|
||||
);
|
||||
assert(
|
||||
indexSource.includes('style.css?v=20260718-wasteland-bg-right-4')
|
||||
&& indexSource.includes('app.js?v=20260718-wasteland-bg-right-4'),
|
||||
'Sidebar interaction assets should share the reviewed cache-busting version'
|
||||
);
|
||||
}
|
||||
|
||||
function assertFrontendGenerationControlsContract() {
|
||||
const source = fs.readFileSync(PUBLIC_APP_PATH, 'utf8');
|
||||
const controlsStart = source.indexOf('function updateGenerationControls()');
|
||||
@@ -811,8 +874,8 @@ function assertFrontendGildedThemeContract() {
|
||||
assert(contrast('#655446', '#fff7ea') >= 4.5, 'Gilded muted text should remain readable on ivory panels');
|
||||
assert(contrast('#fff7ea', '#7a3f20') >= 7, 'Gilded primary action text should reach AAA contrast on copper');
|
||||
assert(themeStyle.includes('@media (prefers-reduced-motion: reduce)'), 'Gilded theme motion should respect reduced-motion preferences');
|
||||
assert(indexSource.includes('style.css?v=20260717-wasteland-hifi-23'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
|
||||
assert(indexSource.includes('app.js?v=20260718-theme-bundle-4'), 'Theme bundle app script should use the current cache-busted asset URL');
|
||||
assert(indexSource.includes('style.css?v=20260718-wasteland-bg-right-4'), 'Theme bundle stylesheet should use the current cache-busted asset URL');
|
||||
assert(indexSource.includes('app.js?v=20260718-wasteland-bg-right-4'), 'Theme bundle app script should use the current cache-busted asset URL');
|
||||
}
|
||||
|
||||
function assertFrontendWastelandThemeContract() {
|
||||
@@ -826,6 +889,8 @@ function assertFrontendWastelandThemeContract() {
|
||||
: '';
|
||||
const themeStyleStart = styleSource.indexOf('/* === 暗金荒野');
|
||||
const themeStyle = themeStyleStart >= 0 ? styleSource.slice(themeStyleStart) : '';
|
||||
const highFidelityStyleStart = styleSource.indexOf('/* === 暗金荒野高保真复刻');
|
||||
const highFidelityStyle = highFidelityStyleStart >= 0 ? styleSource.slice(highFidelityStyleStart) : '';
|
||||
const themeOption = source.match(/\{\s*value: 'wasteland',[\s\S]*?\n\s*\},/);
|
||||
|
||||
assert(themeOption, 'Frontend should register the wasteland theme option');
|
||||
@@ -853,6 +918,7 @@ function assertFrontendWastelandThemeContract() {
|
||||
'--wasteland-cut: polygon(',
|
||||
].forEach((token) => assert(themeVars.includes(token), `Wasteland theme is missing semantic token: ${token}`));
|
||||
assert(themeStyleStart >= 0, 'Stylesheet should include a dedicated wasteland component layer');
|
||||
assert(highFidelityStyleStart >= 0, 'Stylesheet should retain the final high-fidelity wasteland override layer');
|
||||
|
||||
const bodyRule = themeStyle.match(/html\[data-theme='wasteland'\] body\s*\{([^}]*)\}/);
|
||||
assert(bodyRule && bodyRule[1].includes("url('assets/themes/wasteland/background.webp')"), 'Wasteland body should render the reviewed local knight background');
|
||||
@@ -1037,7 +1103,14 @@ function assertFrontendWastelandThemeContract() {
|
||||
});
|
||||
|
||||
assert(!/url\(\s*['"]?https?:\/\//i.test(`${themeVars}\n${themeStyle}\n${JSON.stringify(manifest)}`), 'Wasteland variables, components and asset manifest should not load remote URLs');
|
||||
assert(/@media \(max-width:\s*720px\)[\s\S]*?html\[data-theme='wasteland'\] body\s*\{[\s\S]*?background-position:\s*center,\s*center,\s*70% 50%;/.test(themeStyle), 'Wasteland 720px layout should reposition and darken the local background');
|
||||
assert(
|
||||
/@media \(min-width:\s*721px\) and \(max-width:\s*1100px\)[\s\S]*?html\[data-theme='wasteland'\] body\s*\{[^}]*background-position:\s*center,\s*center,\s*75% 50%;/.test(highFidelityStyle),
|
||||
'Wasteland 721-1100px layout should reveal the right side of the local background'
|
||||
);
|
||||
assert(
|
||||
/@media \(max-width:\s*720px\)[\s\S]*?html\[data-theme='wasteland'\] body\s*\{[^}]*background-position:\s*center,\s*center,\s*75% 50%;/.test(highFidelityStyle),
|
||||
'Wasteland 720px layout should keep the local background right-aligned'
|
||||
);
|
||||
assert(/@media \(max-width:\s*560px\)[\s\S]*?html\[data-theme='wasteland'\] \.input-wrapper\s*\{[\s\S]*?min-width:\s*0;[\s\S]*?min-height:\s*48px;/.test(themeStyle), 'Wasteland 560px layout should keep the composer within the viewport');
|
||||
assert(/@media \(prefers-reduced-motion:\s*reduce\)[\s\S]*?html\[data-theme='wasteland'\] \*[\s\S]*?animation:\s*none !important;[\s\S]*?transition:\s*none !important;/.test(themeStyle), 'Wasteland motion should respect reduced-motion preferences');
|
||||
|
||||
@@ -1055,8 +1128,8 @@ function assertFrontendWastelandThemeContract() {
|
||||
assert(contrast('#c9bda6', backgroundColor) >= 4.5, `Wasteland muted text should reach AA contrast on ${backgroundColor}`);
|
||||
});
|
||||
|
||||
assert(indexSource.includes('style.css?v=20260717-wasteland-hifi-23'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
|
||||
assert(indexSource.includes('app.js?v=20260718-theme-bundle-4'), 'Wasteland registration should share the cache-busted theme bundle URL');
|
||||
assert(indexSource.includes('style.css?v=20260718-wasteland-bg-right-4'), 'Wasteland stylesheet should share the cache-busted theme bundle URL');
|
||||
assert(indexSource.includes('app.js?v=20260718-wasteland-bg-right-4'), 'Wasteland registration should share the cache-busted theme bundle URL');
|
||||
}
|
||||
|
||||
function assertFrontendCcwebPromptContract() {
|
||||
@@ -2730,6 +2803,11 @@ async function main() {
|
||||
console.log('Wasteland theme regression checks passed.');
|
||||
return;
|
||||
}
|
||||
if (regressionTarget === 'sidebar-collapse') {
|
||||
assertFrontendSidebarCollapseContract();
|
||||
console.log('Sidebar collapse regression checks passed.');
|
||||
return;
|
||||
}
|
||||
if (regressionTarget === 'runtime-image-send') {
|
||||
await runRuntimeImageSendTarget();
|
||||
console.log('Runtime image send regression checks passed.');
|
||||
@@ -2748,6 +2826,7 @@ async function main() {
|
||||
assertUnlimitedImageAttachmentsContract();
|
||||
assertFrontendGildedThemeContract();
|
||||
assertFrontendWastelandThemeContract();
|
||||
assertFrontendSidebarCollapseContract();
|
||||
assertFrontendGenerationControlsContract();
|
||||
assertFrontendComposerMcpContract();
|
||||
assertComposerSlashRoutingContract();
|
||||
|
||||
Reference in New Issue
Block a user