Files
tamabox/.workbuddy/memory/2026-10-19.md
T

615 lines
49 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 2026-10-19
## 部署后复测(强制严格档那批)
- 规则页返回首页已居中:线上 `<div class="uk-margin-large-top uk-text-center">` ✔
- 验证码弹窗按钮已缩短:线上 `>送信<` ✔(旧的「認証して送信」已消失)
- 箱主面板 `/user/questions`:限制期条幅 + 「查看社区规则」链接都在 ✔
- 严格度锁定(admin6 当前 L2):
- `/user/profile` 出现「处置期间审核严格度已锁定为最高档,解除后自动恢复你的原设置」✔
- `select[name=strictness]` 与「保存审核严格度」按钮均带 `disabled` ✔
- 直接 POST `/user/moderation-strictness/update?tab=security` + `strictness=lax` → 302 回 profile,
flash 仍是「已锁定」,且选中档仍为 `strict`(说明未写入)✔
- 踩坑:表单 action 是 `/user/moderation-strictness/update`(**没有** `/profile` 段);用错路径返回 302 到 `/`,
容易误判成「被中间件拦」。另外每次 POST 都要重新取 `_csrf`,用上一页(已跳首页)的 token 会 400。
## 记忆维护
MEMORY.md 一度 19KB 被注入截断,已重写压缩到约 4.9k 字符(8.1KB),保留构建/行尾/embed/审核/处置/CLI 等硬约束,
删去已在日志里的过程细节。
## CLI 文档 + 全量代码审阅(同日)
新增 `docs/cli.md`:命令总览、reports 三种用法与口径、restrict 级别语义/副作用、unrestrict、
hide-public、ban/restore、典型处置流程、排错表、明确不做的事。
审阅中发现并修复(全部本地临时库实测):
1. **`hide-public` 举报闭环静默失效**(真 bug):`build()` 带 `is_private=false`,在 Update 之后拿它当子查询恒为空集
→ 隐藏成功但 `content_reports` 仍 pending。改为 Update 前 `Pluck("id")` 快照,再按 ID 更新/标记。
实测:3 条 pending → accepted,未波及其他箱主。
2. **整箱分享卡未拦截**:`ShareCard` 直接走缓存,L1–L3 仍能生成整箱图。已在缓存查询前加拦截(fail-closed)。
3. **L2 通知邮件误导**:强制私密后仍给提问者发「你的提问有回答」,链接点开 302。改为 `!forcedPrivate` 才发。
4. **L2 写入顺序**:`SetPrivate` + `AnswerByID` 两条语句有「公开+已作答」窗口,第二条失败还会留下「私密+无回答」。
新增 `Questions.AnswerByIDPrivate`(单条 UPDATE 同时写 answer/is_read/is_private),并补测试 `TestQuestions/AnswerByIDPrivate`。
5. **`ListAPI` 绕过 Pager**:L4 停用与页面访问密码只挡 HTML,`/api/v1/user/{domain}/questions` 仍能整页拉。
已补两道校验(均返回「用户不存在」,不暴露原因)。**页面密码可被 API 绕过是升级前就存在的问题。**
6. **L1 曝光残留**:`GetShowInHome` 的 Count 未过滤(分页总数虚高、末页空白)、热门标签查询未过滤。已补同一子查询。
7. **性能**:`ActiveLevel` 由两次查询改一次(取最高 rank);新增 `moderation.EffectiveStrictnessFor(level, raw)`,
发布路径上不再重复查库(原先一次发布最多 4 次处置查询)。
8. **证据包口径**:分母是「累计公开回答」不是窗口产出 → 文案加「累计」;新增 `待处理 N` / `已处理` 标记,
避免把已闭合的举报误读成还挂着;补 `q.deleted_at IS NULL`;`--stats` 说明最长回溯 30 天;CLI 对 `active` 做 nil 守卫。
验证:`go build ./...` EXIT=0;db/i18n/templates 测试全绿;CLI 全流程(restrict L3 → 证据包 → unrestrict)实测通过;
临时库已 DROP。
遗留(未改,已向用户说明):L4 不覆盖 `/vote/{token}`;`restrict` 没有 `--days` 上限;处置不发邮件给箱主(只有登录后条幅);
`abuseAlertCooldowns` 进程内 map 无清理(重启即失效)。
## 第二轮:处置通知邮件 + 超长确认 + 冷却表清理
- 新增 `templates/mail/restriction-notice.html` + `mail.SendRestrictionNoticeMail(RestrictionNotice)`:
文案由调用方按**箱主自己的语言**(`users.language`,空则 `conf.App.DefaultLang`)预翻译后传入,
模板本身单语言。**不含 `--reason` 原文**,只有级别名/到期/规则链接/申诉渠道。
i18n 新增 10 个键 ×4 语言:`mail.restriction.*`(6)+ `restriction.level.L1..L4`。
- `restrict`:新增 `--force`(`--days > 90` 必填,否则直接报错)与 `--no-notify`;交互模式下超长处置
还要再输一次级别名(`--force --yes` 视为两次确认)。发信失败只 warn + 终端提示,**不回滚处置**;
发信加了 20s 超时(gomail 无 Timeout 字段,裸 Dial 会等系统 TCP 超时)。
- `abuseAlertCooldowns` 加惰性清理(>1024 条时删除已过 24h 的项)。
- 顺带:`/user/questions` 限制期条幅改用 `restriction.level.*` 的名称(「收窄公开」)而不是裸 `L2`。
验证:真实 SMTP sink 实收两封(ja 与 zh-CN),标题/级别名/到期/规则链接均正确、`--reason` 未出现、
链接未被 `ZgotmplZ` 转义;`--days 120` 无 `--force` 被拒;黑洞 SMTP 下 23s 内返回(未阻塞两分钟)。
`go build ./...`、`go vet`、db/i18n/templates 测试、checktpl 全绿。
遗留:`unrestrict` 不发通知(只解禁);`restrict` 对 `--days` 无上限(设计如此)。
## 第三轮:解除处置也发通知
- 复用同一封模板 `mail/restriction-notice.html`:`appeal` 段改为 `{{if .appeal}}` 可选,
解除通知不传该字段就整段省略。
- 新增 i18n 键 ×4:`mail.unrestrict.heading/body/note`(body 带域与级别名;
note 明确写「处置记录保留用于审计、私密内容不会自动恢复」)。
- `unrestrict` 新增 `--no-notify`;撤销前先 `ActiveLevel` 取级别(撤销后读不到),
撤销后按箱主语言发解除通知。`affected==0` 直接返回、不发信。
- 抽出公共件:`sendNotice`(20s 超时)、`reportNoticeResult`(统一成功/失败输出)。
- 文档补充:`unrestrict` 的 `--no-notify`、解除通知内容、以及**到期自动解除不会发信**(无调度器)。
实测(本地 SMTP sink,2 封):处置信含级别名/到期/申诉段;解除信含「已解除」+ 私密内容提示、
**无申诉段**;两封都不含 `--reason`。`--no-notify` 两处都不发信;无生效处置时不发信。
`go build ./...`、db/i18n/templates 测试、checktpl 全绿。
## 上线前最终验收(部署后实测)
- 公开页:`/` `/rules` `/restricted` `/deactivated` 全部 200;`/rules` 四语言标题正确。
- 箱主面板(admin6,L2):条幅显示「收窄公开」+ 规则链接;严格度面板锁定提示 1 处、下拉 disabled。
- **L2 写入路径实测**(关键,因为改成了 `AnswerByIDPrivate` 单条 UPDATE):
对未回答题 q95 作答 → 302 回题目页 → flash「已保存为私密:当前处于平台处置期,回答不会公开」→ 匿名访问 302。
- 公开列表 API `/api/v1/user/{domain}/questions` 正常返回 `{code,data:{data,next_cursor,has_more}}`(新增的 L4/密码校验没有打断正常路径)。
- 整箱分享卡:受处置箱主 302(拦截生效);未处置箱主 500,原因是**测试站未配置字体**(`分享图生成失败:字体文件未配置或无法加载`),与本次改动无关。
这一点反而证明拦截位置在生成之前。
- 未能自测:CLI 发信(restrict/unrestrict 通知)需要在服务器上跑命令,只能由用户执行。
注意:admin6 现有测试题 73/88/89/90/94/95;其中 95 已被我回答并存为私密(内容带 `restrict self-check`)。
admin6 的 L2 于 2026-10-19 21:29 自动到期。
## 邮件模板统一(用户反馈:页脚与其它邮件不一致)
- 原因:`restriction-notice.html` 是我新写的,页脚用了自造的 `© 2026 TamaBox`,与项目其它 11 封邮件的
「此邮件由系统自动发送,请勿直接回复。<br/>{{ .year }} 狼的提问箱」不一致。
- 处理:整封模板按 `question-reported.html` 的结构重写(24px 标题 + `{{.time}}` 副行、同样的卡片/页脚/外间距),
`time` 由 `mail.SendRestrictionNoticeMail` 统一注入(调用方不用各自拼时间)。
- 实测(本地 SMTP sink):页脚文本与 question-reported 完全一致,`© ... TamaBox` 已消失,头部时间行存在。
- 注意:站点名在所有邮件模板里都是硬编码「狼的提问箱」,不是配置项;改站名要一起改 12 个模板。
## 页脚精简(移动端一行放不下)
- 去掉「盘」链接(`https://share.shiroko.one`,zh 文案就叫「盘」),并删除已无引用的
`site.footer.drive` 键(四语言同步删,保证 `TestDictionaryComplete` 词条数一致)。
- `site.footer.rules`:zh-CN「社区规则」→「规则」,zh-TW「社群規則」→「規則」(en/ja 本就短)。
- 现在页脚一行:规则 | 找站长 | 公告 | 聊天 | 状态 | 主页。
- 注意:`templates/base/footer.html` 用 `|` 分隔,行首行尾各带一个,删整行时不会留下悬空分隔符(已核对)。
## 页脚改两行(用户改主意:盘恢复)
- 恢复 `https://share.shiroko.one`「盘」链接与 `site.footer.drive` 键(zh-CN 盘 / zh-TW 盤 / en Drive / ja ドライブ)。
- `site.footer.rules` 文案恢复:社区规则 / 社群規則(en/ja 不变)。
- 布局两行(靠 `<br>` 断行,不用额外 CSS):
第一行 `社区规则 | 找站长`,第二行 `盘 | 公告板 | 聊天室 | 监控板 | 导航页`。
- 键插在 `site.footer.rules` 之后(组末),空格数按同长度键复制,gofmt 对齐不变;dict 仍 CRLF。
- 验证:build / checktpl / i18n / templates 测试全绿。
## 页脚改为「从少到多」四行
版权行与存活计时移到最后两行(原来在最上面),整块按文字量递增:
`社区规则 | 找站长` → `盘 | 公告板 | 聊天室 | 监控板 | 导航页` → `Powered by NekoBox | ICP` → `提问箱已成功存活:…`。
实现只调整了 `<br>` 的位置,仍共 4 个 `<br>`,所以块高与顶部空行跟原来一致(未新增占位)。
注意:`box_runtime` 的脚本按 id 取元素,移动位置不影响;但 `<br>` 数量别随意增删,否则页脚高度会变。
## 前端样式体检(只出结论,未改代码)
量化事实(脚本统计 templates/**/*.html):
- 全站 **0 个 .css 文件**,样式全部内联:`base/header.html` 内含 **17.5KB CSS + 10.6KB JS**(模板总 42.5KB),
footer 3.0KB JS;全模板共 **24 个 `<style>` 块**,`user/profile.html` 自身还有 11.2KB CSS + 22.8KB JS(总 62KB)。
- `--tb-*` 设计变量 **只在 `html[data-theme="dark"]` 里定义**,light 主题没有 `:root` 一套;
调用处全靠 fallback(`#e9e9e9` / `#e5e5e5` / `#fafafa` / `#333` / `#666` 混用),explore 等页面还写死色值。
- **深色模式 FOUC**:`applyTheme(getTheme())` 在 body 内、`<nav>` 之后执行(header.html:812),
深色用户刷新时顶栏会先白闪一下。修法:把 theme 初始化内联脚本提到 `<head>`。
- 结构:`uk-container-xsmall`(750px)由 header 统一输出,仅 explore/vote/chat 页面自套 small;
`question/list.html` 仍用 `<center>`、内联 `style="min-height:260px"`,用户名/简介颜色由用户自选压在背景图上,无 scrim → 可读性无保障。
- 首页 `home.html` 是纯 UIkit 卡片 + `<hr>` + `style="color: red"` + `<pre>` 统计 + 首页挂 Waline(250 行 locale)。
建议分档(未实施):P0 = light/dark 双套 token + 圆角/阴影/中文字体栈 + 首页重排 + 背景图 scrim;
P1 = header CSS 抽成 `static/css/*.css`(注意 `embed.go`/`static/fs.go` 都是显式清单)+ FOUC 修复;
P2 = 图标统一(UIkit icons 取代 emoji/位图 png)、标签减量、viewpoint/meta 去重。
## P0 前端美化(已实施)
用户拍板:卡片**结构不变,只加圆角和柔阴影**;先做 P0,不抽 CSS 文件。
改动文件:
- `templates/base/header.html`
- head 顶部新增**主题初始化内联脚本**(深色用户不再白闪;原 `applyTheme` 在 body 里、nav 之后执行)。
顺手删掉 body 脚本里的 `applyTheme(getTheme())`,只保留切换逻辑。
- 新增**设计令牌块**:`:root` 亮色 + `html[data-theme=dark]` 暗色,同名 14 个(bg/surface/surface-2/surface-hover/
border/text/text-secondary/text-muted/primary/primary-weak/radius/radius-sm/radius-xs/shadow/shadow-hover/font)。
原暗色块里的 7 个变量定义已删除(现在统一在令牌块)。
- 新增**基础质感层**:html+body 底色与字体栈(system-ui + 苹方/冬青黑/雅黑)、卡片 12px 圆角 + 双层柔投影、
按钮/输入 8px、标签 6px、alert 10px、modal 14px、导航栏白底、`:focus-visible` 描边、`tabular-nums`、
`prefers-reduced-motion`。
- `.tb-cover`:封面遮罩;默认浅色(配 `.uk-light` 的黑字兜底),`.tb-cover-dark` 为深色。
- `.tb-img-uploader` 上收到 header(原来 item / new-question 各写一份**内联 style**,写死 #f8f8f8 → 深色下露白)。
- `templates/home.html`:整页重排为 hero(标题+一句话+探索/规则按钮)+ 3 张功能卡 + 域名迁移公告
+ 提醒列表 + `<details>` 折叠更新日志 + 3 个数字统计卡(`.SiteStats` 数值字段,30 分钟 JS 刷新改为按字段更新)。
全部原有信息保留;页内 `style=` 归零。
- `templates/question/list.html`:封面加 `.tb-cover`(仅当有背景图),新增 12 行 JS 按用户名颜色亮度
决定遮罩方向;`min-height:260px` 内联改为 `.tb-profile-cover`。
- 6 个页面样式块里的硬编码色值收敛到令牌(explore/question-list/profile/list/item/vote-page,共 ~84 行),
CRLF 文件用字节级替换(未改动行尾)。
验证:`go test ./templates/... ./internal/i18n/...` 全绿;`go build`(overlay)通过。
新增 `templates/zz_preview_test.go`:`TEST_PREVIEW=1 go test ./templates -run TestGenPreview` 产出
`outputs/preview-{home,box,explore}-{light,dark}.html`,用无头 Chrome 截图核对过亮暗两套。
⚠️ 本机 **Chrome 新版 headless 会挂住**,必须 `--headless=old`,且页面外部资源要本地化
(`outputs/preview-assets/`,从 cdnjs.snrat.com 取 UIkit 3.3.3;cloudflare/jsdelivr 在本机不通)。
## 封面遮罩 P0 缺陷修复(重要)
**症状**:`.tb-cover::after` 遮罩不渲染,且**完全不报错**——截图对比像素只差 ±1(只看着像"没效果")。
**根因**:UIkit 3.3.3 自带
`.uk-flex::after,.uk-flex::before{display:none}`(优先级 0,2,0),
而封面元素是 `uk-flex`,我写的 `.tb-cover::after`(0,1,1)优先级不够 → 被压成 display:none。
定位方法:`getComputedStyle(el,'::after').display` 得到 `none`;再逐个 `classList.remove` 试 →
去掉 `uk-flex` 立刻变 `block`。最后在 uikit.min.css 里 grep 到那条规则。
**修法**:选择器写成 `.tb-cover::after, .tb-cover.uk-flex::after` 并显式 `display:block`。
**实测效果**(无头 Chrome 截图 + 像素测量,非目测):
- 深色渐变背景 + 白字:封面底部亮度 0.229→0.102,白字对比度 **3.77→6.89**(过 WCAG AA 4.5)
- 线上默认背景(`box_background.png` 2048×1152,接近全白)+ 黑字:0.912→0.971,对比度 19.25→20.42,
即**对默认背景几乎没有可见影响**
- 亮度判定 JS 已验证:`#fff` 会加 `tb-cover-dark`,`#000` 不加。
**教训**:CSS 改动不能只看"规则写上了/类加上了",必须量像素。伪元素 + UIkit 工具类组合要在计算样式层验证。
## 账号背景图的真实链路(回答用户提问时查证)
- 注册时把 `[upload] default_background` **一次性复制**进 `users.background`(`route/auth/register.go:51`),
不是每次渲染兜底 → 之后改配置**不影响存量用户**。
- 页面只用 `.PageUser.Background` 渲染 `data-src`;为空则无背景图、无遮罩。
- 分享卡是另一条路:`route/question/share.go:345` 用 `user.Background`,为空时回退 `conf.App.ShareBackground`。
- 线上 `box.shiroko.one/_/tamakyi` 实际值 = `https://r2.337845818.best/tamabox/box_background.png`。
- 预览里那张"渐变图"是 `zz_preview_test.go` 造的测试数据(data: URI 渐变),**不是产品行为**;
顺带发现 Go 模板会把 `data:` URI 在属性上下文里替换成 `#ZgotmplZ`(真实 http URL 不受影响)。
## 设置页「防护设置」缺白底(用户报)
根因:其它面板都包在 `<section class="tb-pcard">`,只有内容防护面板(profile.html:842)是裸 `div.profile-panel`,
直接露出页面灰底 `--tb-bg`。修法:包一层 `section.tb-pcard`,并把 `.tb-pcard` 的 `background:#fff` 改成
`var(--tb-surface)`(删掉随之冗余的 dark 覆盖)。
实测(计算样式):account 与 security 面板 bg 亮色均 `rgb(255,255,255)`、暗色均 `rgb(36,40,48)`,圆角 10px,完全一致。
顺手把 `security` 加入 `render_test.go` 的面板渲染测试列表(原来只测 account/question/chat/vote)。
⚠️ 教训:用脚本批量缩进/hash 插入时先 dry-run。这次第一版脚本把「空行」也当成需要写 `<section>` 的行,
一次性插了 5 个多余标签;靠立刻 `sed` 复查发现并回滚。
## P1 第一步:CSS 抽离(已完成)
- 新增 `static/css/base.css`(23KB,含注释;gzip 后约 6KB),内容 = 原 header 里的
设计令牌 / UIkit 补丁 / 深色覆盖 / 质感层 / 顶栏响应式 / 标签 / Markdown / 进度条 / 移动端底栏。
- `header.html`:内联 `<style>` 从 17.5KB 降到只剩 1 个(算数验证码,跟着 `{{ if CaptchaType }}` 条件渲染);
改为 `<link href="/static/css/base.css?v=20261019a">`,位置在 UIkit 样式之后(保证覆盖顺序)。
单页 HTML 42.5KB → 30.9KB。
- `static/fs.go` 的 `//go:embed` 显式清单加 `css/base.css`(漏了就是运行期 404 + 全站无样式,编译期不报错)。
- 静态缓存沿用既有 `CacheControl: public, max-age=86400`;靠 `?v=` 版本号破缓存。
**验证方式**:改动前后各截一次同样尺寸的截图做像素比对。
home-light / home-dark / explore-light 三页差异均 **0.024%**,bbox 只有 (11,56)-(29,74) 一个 18×18 方块
= 导航栏随机动物 emoji(每次加载都变),不是样式回归。go build + 模板/i18n 测试全绿。
## 测试环境复测(P1 CSS 抽离 + 防护面板白底,已上线)
- `GET /static/css/base.css?v=20261019a` → 200 / `text/css` / **23088 字节,与本地 `static/css/base.css` 逐字节一致** /
`Cache-Control: public, max-age=86400` / gzip 后 7109 字节。
- 首页 32747 字节(改动前 52747),gzip 10534(改动前 13642);页面内联 `<style>` 只剩验证码 + 首页自己的样式块。
- 设置页(登录 admin6):`data-profile-panel="security"` 下已有 `<section class="tb-pcard">`;
用线上 HTML + 本地 CSS 复算样式 → account / security 两块 `bg=rgb(255,255,255)`、`border-radius:10px`,页面底 `rgb(247,248,250)`。
- `/_/admin6` 公开页:`class="... uk-light tb-cover"`,`:after` 计算值 **display=block, width=689px**
→ 遮罩在真实部署上确实渲染了(UIkit `.uk-flex::after{display:none}` 的坑已解决)。
该用户用户名为 `rgb(0,0,0)`,走浅色遮罩(符合预期)。
- 各页 200:`/`、`/explore`、`/rules`、`/user/questions`;`/user/vote-form` 无参 302 属正常。
- 首页首次访问总字节略增(10.5+7.1 vs 13.6KB gzip),但 CSS 跨页缓存后每页少 3.1KB,且服务端少渲染 17.5KB 模板。
## 专属测试账号(用户指定)
- `https://box-dev-api.337845818.best/_/admin6`,邮箱 `[email protected]`,密码 `adminadmin`。
- 登录需算数验证码,curl 流程:GET /login 取 `_csrf`(同时存 cookie)→ `GET /api/captcha/generate` 拿 `{id, question}`
→ 算答案 → `POST /api/captcha/verify`(头 `X-CSRF-Token`,体 `{id, answer}`)→
`POST /login`(email/password/_csrf/`g-recaptcha-response=id:answer`)→ 302 到 `/`,cookie 已带登录态。
## P2 一致性收尾(已实施,未部署)
1. **导航图标**:🌐🌙☀️ emoji → 内联单色 SVG(`stroke: currentColor`,跟随主题/悬停色)。
⚠️ 原计划用 UIkit icons,实测 **UIkit 3.3.3 的图标集里没有 moon / sun**(只有 world),
照写会渲染成空白按钮——所以三个图标全部改成内联 SVG,也不依赖 JS。
2. **标签**:投票类型 20 处 `uk-label uk-label-primary`(实心蓝)→ `.tb-chip` 轻量胶囊;
探索页热门标签原来的 5 色轮换(success/warning/danger/primary/secondary)也统一成 `.tb-chip`。
箱主面板 `question-list.html` 另 6 处字节级替换(CRLF 保留)。
保留:置顶/私密/被举报/已读 这些**语义状态**仍是彩色 uk-label(有含义,不该降级)。
3. **遗留写法**:`<center>` ×2 → `div.uk-text-center`;占比条从
`div.uk-progress > div.uk-progressbar(style:width)` 改成 UIkit 3 原生
`<progress class="uk-progress" value=".." max="100">`,**删掉 25 行 UIkit2 兼容补丁**,
只保留 10px 高/6px 圆角的外观收窄。测试 `percent_check_test.go` 的断言同步改成 `value="66" max="100"`。
4. **meta**:删掉重复的 `msvalidate.01`(保留 head 顶部一份)、viewport 补 `initial-scale=1`。
**踩坑**:暗色主题图标原来写 `display:inline`,span 里放块级 `<svg>` 会让导航栏高度变化 → 整页下移 36px。
用行亮度曲线做互相关定位到 dy=36,改成 `inline-flex` 后复测差异回到 0.059%(只剩随机 emoji)。
教训:**改图标尺寸/显示模式一定要量整页位移**,不能只看局部截图。
## P2 线上复测(2026-10-19 12:2x)
- `GET /static/css/base.css?v=20261019c` → 200 / text/css / **23918 字节,与本地逐字节一致**,页面已引用该版本。
- meta:viewport 已带 `initial-scale=1`;`msvalidate.01` 只剩 1 份。
- 图标:首页 emoji 🌐🌙☀️ 归零,`<svg ... stroke="currentColor">` ×5(语言 1 + 桌面主题 2 + 移动主题 2);
计算样式暗色下 `#theme-toggle .tb-icon-dark` = `display:flex`、亮色图标 `none`;
**导航高度亮/暗均 80px** → 上次 36px 位移问题确认修好。
- 探索页:`.tb-chip` ×25(全部有底色 `rgb(244,246,248)`、文字 `rgb(92,99,112)`、圆角 6px、字号 11.52px);
残留彩色 `uk-label` 4 个,均为置顶/私密等语义状态。
- 结果页:`/user/votes/{id}/results` 原生 `<progress class="uk-progress" value max>`,计算高度 **10px**、圆角 6px;
遍历 10 个结果页 `uk-progressbar` 全部为 0。
- 巡检 `/ /explore /rules /login /register /_/admin6 /_/admin3` 均 200。
## P2 补漏(已改,待部署)
复测时发现 **结果页与参与页的题型标签仍是实心彩色** —— 那两处的 class 由 Go 模板函数
`fieldTypeLabelClass` 生成,我上一轮只按字面 `class="uk-label uk-label-primary"` 搜索,漏了它们。
- `templates/user/vote-results.html`(2 处)、`templates/vote/page.html`(2 处):序号 + 题型 → `.tb-chip`;
状态标签(未开始/已结束/进行中/已参与)保留彩色,属语义。
- 删除 `internal/template/template.go` 的 `fieldTypeLabelClass`(19 行,已无引用)。
顺带查明:该函数 `text/textarea` 走 `return ""`,注释说"默认灰",**实际渲染成 UIkit 默认蓝**,
所以文本框标签一直是蓝的——注释与实现不符。
- `.uk-progress::-webkit-progress-value` 圆角改成两端都圆(`-moz-progress-bar` 同步)。
⚠️ 起因是误判:我先前测"右端方角"时像素扫描循环上界写成 `range(110,830)`,恰好等于条宽,
于是右端永远"恒定不变"→ 假象。放宽到整幅宽度后两版端点曲线完全一致(830/832/833/834/833/832/830),
即**没有视觉 bug**。保留改动只为 Firefox 的 `-moz-progress-bar` 保险。
- `base.css?v=` → `20261019d`。`go build` + `./templates ./internal/i18n ./internal/template` 全绿。
**教训(重要)**:像素测量脚本的扫描范围必须覆盖整幅图,循环上界不能与被测对象边界重合,
否则会把"边界截断"读成"图形特征"。之前 3.77→6.89 那组对比不知是否也受此影响,需复查时再确认。
## P2 补漏部署后复测(12:48)
- `base.css?v=20261019d` 上线,24023 字节与本地逐字节一致。
- 删掉 `fieldTypeLabelClass` 后**无运行期报错**:公开投票页 3 个(200,tb-chip 8/16/4,uk-label 各 1 = 状态)、
结果页 4 个(200,tb-chip 8/2/0/16,彩色 label 全 0,progress 7/0/10/19)、
草稿预览页 2 个(200,tb-chip 2/6)。
- 巡检 `/ /explore /rules /user/questions /user/profile?tab=security /_/admin6 /_/admin3` 全 200 无模板错误。
- `/user/votes/{id}/preview` 对**已发布**投票 302 到 `/vote/{token}` 是设计如此(vote.go:1093),不是回归。
- 截图核对:结果页「1 単選 / 2 多選」已为浅色胶囊,进度条 10px 圆角 6px;参与页字段头同样统一。
## "移动端语言/主题按钮不见了"排查(结论:非缺陷)
实测(线上测试环境,`--force-device-scale-factor=1`):
- 宽 360/390/414/640 → 顶栏 `.tb-nav-utils` = `display:none`,底栏 `#tb-mobile-utils` = `flex`,
rect `0,639,W,46`,语言 `block`、主题按钮 `block`(48×30,SVG 16×16),命中测试落在 `mobile-lang-select`。
- 宽 700/768/980 → 底栏 `none`,顶栏 `flex`,右上角地球 + 月亮都可见(截图 `outputs/t-nav-right.png`)。
- 页面级差异:`/_/admin6` 底栏**只有月亮没有语言下拉** —— `IsUserPage=true` 让语言块整体不渲染(桌面同样没有)。
- 生产 `box.shiroko.one` 仍是旧构建(页面无 `base.css` 引用,底栏是 emoji 月亮),移动端同样正常。
用户确认是忘了「移动端移到固定底栏」这个设计,不是 bug。
留下的真实缺口:**箱主页(最常见的外部入口)完全没有语言切换**,见 MEMORY.md 前端样式条目。
## 修正:箱主页无语言切换是设计,不是缺口
上一节把它记成"真实缺口"是错的。用户明确:**箱主页属箱主个性化,以箱主配置为主**,
语言入口不出现是有意为之(`IsUserPage` 门控)。
MEMORY.md 对应条目已改为"有意设计、别当 bug 修、要动先问"。
## 备份(用户要求)
`../TamaBox-v4-mainline-20261019-1445.tar.gz`(940K,302 条目,排除 .git/.workbuddy/outputs)。
P1 剩余两项(首屏跟随系统深色、首页 Waline 懒加载)用户明确**先不做**。
## 主题系统方案(只出设计,未实现)
用户诉求:箱主在后台选主题、仅对箱主相关页面生效;站点级默认主题在配置里;要一份可复用的主题底稿。
已确认的现状事实(设计依据):
- 设计令牌只有 16 个,集中在 `static/css/base.css` 的 `:root`(亮) + `html[data-theme="dark"]`(暗)。
- `<html lang>` 在 `templates/base/header.html:2`,服务端渲染;主题属性可放在这里(无 FOUC)。
- `question.Pager`(`route/question/page.go:45`)是 `/_/{domain}` 与 `/_/{domain}/{id}` 的共同中间件
→ 打主题标记的唯一合适位置。
- `/user/*` 与 `/_/*` 共用 `IsUserPage`(context.go:241 按 URL 前缀判定),所以「箱主相关页面」
≠ IsUserPage,必须单独区分,否则设置页会被主题化。
- `static/fs.go` 与 `templates/embed.go` 都是显式 embed 清单(新增主题 css 必须手加)。
- 用户模型已有大量个性化字段(usernamecolor/introcolor/background…),主题作为 `users.theme` 同族最自然。
- 分享卡是服务端绘制图片(`route/question/share.go`),不经过 CSS → 主题不覆盖它。
关键结论(写进方案的核心):令牌覆盖必须用
`html[data-theme-style="x"]`(0,1,1,压过 `:root` 的 0,1,0)+
`html[data-theme="dark"][data-theme-style="x"]`(0,2,1,压过 base 的 dark)。
只写浅色块会让深色模式被浅色令牌污染(同特异性 + 主题文件后加载)。
## 主题系统初版(已实现,未部署)
用户拍板:①作用范围跟随箱主配置(即 /_/ 箱主相关页面)②主题只做配色/图案/整页背景,**不涉及布局**
③主题名可本地化 ④分享卡不参与。
实现:
- `internal/theme`:注册表 + 三级回退。`[theme] default/available` → 白名单(slug 正则 + css 是否存在),
`Resolve(用户值)`:`default`→空串(不加载)/命中→该 slug/其余→站点默认。"" 与 "default" 语义不同:
""=跟随站点默认,"default"=箱主明确选基础外观。
设置页色块从主题 css 浅色块里抓 `--tb-bg`/`--tb-primary` 拼渐变(值必须过色值正则,防注入)。
- `users.theme varchar(32) default ''`(AutoMigrate 加列)+ `UpdateTheme` 单列更新。
- `question.Pager` 注入 `Data["ThemeSlug"]`(`/_/{domain}`、单题、密码页、分享卡路由共用这一个中间件);
header.html 在 `<html>` 输出 `data-theme-style` 并条件加载 `themes/<slug>.css`。
- 设置页账号面板末尾新增「主题外观」卡片(独立表单 `POST /user/profile/theme`,不塞进 UpdateProfile,
避免与该面板其它字段互相覆盖);`{{ if .Themes }}` 包裹,未配置主题时不显示。
- 静态版本号统一 `conf.StaticVersion = "20261019e"`(base.css 与主题 css 共用,改样式只改一处)。
- `static/fs.go` 改嵌目录 `css/themes`(以后加主题文件不用再改 embed 清单)。
- 主题文件:`_scaffold.css`(底稿,含 19 令牌说明 + 素材规格 + 特异性规范)、`default.css`(恒等,仅色块预览)、
`sakura.css`(示例,纯 CSS 网点图案 + 粉调)。
- base.css:body 接 `--tb-page-image` / `--tb-page-pattern` / `--tb-page-pattern-size` 两层背景;
**UIkit 主色是编译期常量 #1e87f0**,新增映射块把 a/uk-link、uk-button-primary、checkbox/radio、input:focus、
uk-tab active 接到 `--tb-primary`(基础主题值相同,默认外观零变化)。
实测(无头 Chrome,box 页 data-theme-style=sakura):
- 浅色 primary `#e5678f`/bg `#fff6f8`/pattern 16px 网点命中;深色 `#ff8ab0`/`#241a1f`,卡片表面各自跟随。
- 未选主题的 box 页:无 data 属性、无 themes link、主按钮仍 `rgb(30,135,240)` → 零回归。
踩坑:
1. 第一版 sakura 页「发送提问」还是蓝色 —— UIkit 组件色写死,令牌覆盖不到,必须按组件映射。
2. 预览里 `{{ .StaticVersion }}` 为空(预览夹具没这个键),导致按固定串替换主题 css 失败;改用正则。
3. `writePreview` 用全文搜 `data-theme="dark"` 判断是否已加属性 → 命中内联主题 css 里的选择器,深色预览没生效;
改为只看 `<html ...>` 开标签。
4. 脚本改 form/user.go 时把插入块写在结构体中间,拼出 `} Language ...`;靠 Read 复查发现并重写。
## 主题 follow-up(部署后反馈两处)
1. **/user/* 不生效主题**:根因是 `ctx.Data["ThemeSlug"]` 只在 `question.Pager`(`/_/{domain}`)里注入,
`/user/*` 完全没走这条链路。修法:`internal/context/context.go` 的 Contexter 里按
`isThemeScope(path) && c.IsLogged` 注入 `theme.Resolve(c.User.Theme)`;
`isThemeScope` 只认 `/user` 与 `/user/`(不能用裸 `HasPrefix("/user")`,会误吞 `/username`)。
新增 `internal/context/context_test.go` 守住路径白名单;page.go 的注释同步改。
公开页(`/`、`/explore`、`/vote/{token}`)**仍不跟随**登录用户主题,保持站点默认。
2. **sakura 显示奇怪**(用户选择:顶栏白与粉底割裂 / 卡片纯白太突 / 底色分布不均):
- base.css 新增令牌 `--tb-navbar`(`:root` 里 `var(--tb-surface)`,亮暗两条 navbar 规则都改成
`var(--tb-navbar, var(--tb-surface))`),主题可单独给顶栏上色。
- sakura v2:底色改**纯色** `#fceef3`(不用渐变——渐变会被读成"不均匀"),
顶栏 `#fff6f9`、卡片 `#fffafc`(暖白,不再是纯白硬切),网点从 16px/0.14 放宽到 24px/0.05。
- 静态版本 `20261019f`;`_scaffold.css` 令牌说明 19→20。
- `profile.theme.hint` 四语言文案改成「提问箱页面 + 本人后台生效,公开页保持默认」。
- `zz_preview_test.go` 抽出 `inlineThemeCSS()`,并新增 `preview-profile-sakura-*`(后台页面也要在主题下检查)。
验证:`go build ./...`(须带 webp overlay)、i18n/theme/context/templates 四个包测试全绿。
渲染法:抓线上 `/_/admin6` HTML → 把 base.css 指到本地新文件、主题 css 内联 → 无头 Chrome `--headless=old` 截图。
## 投票页 + 标签主色(部署复核后第二轮)
- `/vote/{token}` 主题:`route/vote/vote.go` 的 `Page` 里本来就会 `db.Users.GetByID(vote.UserID)` 取 owner,
直接在同一分支补 `ctx.Data["ThemeSlug"] = theme.Resolve(owner.Theme)`;box 页 / /user 后台 / 投票页三处
全部由发布者配置决定。GetByID 失败(箱主已删)保持站点默认外观。
- **`uk-label` 默认色就是 UIkit 主色 #1e87f0**:主题下提问管理的「未回答 / 未读」两枚胶囊还是蓝的。
base.css 追加
`.uk-label:not(.uk-label-danger):not(.uk-label-warning):not(.uk-label-success):not(.uk-label-secondary)`
→ `--tb-primary`。**必须用 :not() 排除语义色**:同特异性且 base.css 在 UIkit 之后,不排除会把
danger/warning/success 一起刷成主色。默认主题实测仍是 (30,135,240),零回归。
- StaticVersion → `20261019g`。
- 线上复核(admin6 专属账号,curl 走完整登录:GET /login 取 _csrf → /api/captcha/generate →
/api/captcha/verify(**answer 必须是数字,传字符串会 400 请求格式错误**)→ POST /login):
`/user/profile`、`/user/questions` 均已输出 `data-theme-style="sakura"` 与 themes link(v=20261019f)。
投票页在 f 版本仍无主题 → 需再部署一次。
## 投票页语言跟随发布者(第三轮)
- 抽出 `applyOwnerLocale(data map[string]interface{}, owner *db.User)`(route/vote/vote.go 顶部):
同时写 `Owner` / `Lang` / `LangCode` / `ThemeSlug`。
签名故意收 Data 而不是 `context.Context`:`Context` 内部字段不可在包外构造,收 map 才能单测
(`route/vote/locale_test.go`,纯内存,不碰 DB)。
- `Page`:owner 查询提到 `FeatureStateOf` **之前**——否则「投票功能已关闭」这类提示先按访客语言生成。
- `Submit`:同样先取 owner 再取 `lang`,保证提交后回跳页的 flash 与页面语言一致;
顺带删掉尾部重复的 `GetByID(owner)`(表单类型通知邮件复用同一个 owner),少一次查询。
- 四语言 `profile.theme.hint` 与 changelog 范围说明补上「你发布的投票页」。
- 验证:vote/i18n/context/theme/templates 全绿;线上 `lang="ja"` + 中文内容的不一致待部署后复测。
### 部署后复测(v=20261019g)
- `/vote/7kcP5kR8YnJC`(admin6):`lang="zh-CN" data-theme-style="sakura"`,base/theme css 均 v=g,
日文串(投票する / 匿名での参加 / ログイン)全部消失 → 语言与主题同步跟随发布者。
- `/ _/admin6`、`/user/profile`:仍为 `zh-CN` + sakura,未回归。
- 隔离性:另取三个 token 抽查 —— 4G3i79OCYa2E / 7l0MwF7cLz4T 为 `lang="ja"`(无语言偏好、无主题),
9J1zgXwQ05Ai 为 `lang="zh-CN"` 且**无** data-theme-style → 主题不会跨箱主泄漏。
- 遗留观感问题(未改,等用户决定):投票页的「允许匿名参与」提示条是 `uk-alert-primary`,
仍是 UIkit 浅蓝,在粉底上偏冷。改它需要「浅底 + 深主色文字」的对比度,现有 `--tb-primary-weak`
叠白后对比度只有 ~2.6:1,得先加一个 primary-strong 令牌,故未动。
### 顶栏配色再修(v=20261019h)
用户反馈「探索/注册链接底色粉色,但整条顶栏是灰色,很突兀」。实测部署版:顶栏 `#fff6f9`、
页面 `#fceef3` —— 近白横带压在饱和粉底上,肉眼就读成「白色/灰色的一条」。无头 Chrome 截图 +
像素采样确认不是 UIkit 默认 #f8f8f8 回退(那说明变量没生效,现象会连带整页失效)。
- sakura 浅色 `--tb-navbar` 直接取页面底色 `#fceef3`(深色同样 `#241a1f`),横带消失;
- 顶栏链接 `#999` 在粉底上偏淡 → 主题内微调为 `--tb-text-secondary`,hover 用 `--tb-primary`
(深色被 base.css 的 `!important` 规则接管,不受影响);
- 顺带 `uk-alert-primary`(匿名参与提示/空列表)的 UIkit 浅蓝是页面上唯一的冷色块,
改为 `--tb-primary-weak` 底 + `#a63a5e` 文字(≈5.3:1)。
- 以上三条都写在 sakura.css 的「组件微调」段,只对 sakura 生效,默认主题零影响。
## 主题「砂狼白子 / shiroko」(v=20261019i)
- `static/css/themes/shiroko.css`:灰蓝银白令牌(浅 #edf2f8 / 主色 #4a7fc1),
组件微调只有两条 —— `.uk-card-default` 右下角水印立绘(`background-size: auto 176px`,纯背景层不改布局)、
顶栏链接色提到 `--tb-text-secondary`;深色不铺立绘(浅色构图在深色下会糊)。
- 素材 `static/images/themes/shiroko/`(68KB):`bg-desktop.webp`(2400×1350) / `bg-mobile.webp`(900×1600) /
`hero-fade.webp`(275×620,16% alpha) / `pattern.svg` / `pattern-dark.svg`。
- **`static/fs.go` 的 embed 清单加了 `images/themes`**(目录项,子目录自动覆盖)——
这是「新增顶层静态目录必须手工登记」的又一次踩点。
- 背景是 PIL 合成的:1280 行的竖向渐变 + 白色径向光晕 + 立绘 30% 贴右下 + 狼 10% / 光环 20% / 纸飞机 18%。
实测 mean≈244、std≈9;右侧留白区 std 13(立绘可见),中间 std 1.9(正文区干净)。
- ⚠️ **`cover` 会按视口裁剪**:竖屏/超宽屏会把贴在图片最右的立绘裁掉,所以立绘不能太靠边 ——
位置要留在图片横向 24%~76% 之外就得配 `@media` 换图(本次桌面 16:9 + 移动 9:16 两张)。
- 生成素材(ImageGen)8 张里只有 3 张可用:线稿图案(狼/光环/纸飞机)抠白底干净;
2 张人像抠底失败(背景不是纯白,`background: transparent` 实际没生效),2 张场景背景几乎没有内容(std<5)。
教训:**没有视觉能力时,生成图要用 `mean/std/edge` 先验内容量**,别直接拿去合成。
- 素材来源与版权写在 `docs/theme-shiroko.md`(角色版权 NEXON / Yostar,非商用同人)。
- 启用需要在 `conf/app.ini` 的 `[theme] available` 里加 `shiroko`(示例配置已更新)。
## shiroko 插画重做:动物线稿 → Q 版人物(v=20261019k)
用户反馈「简笔画不是画动物,是人物形象」,插画层整体重做。
- 新增 `dev/gen_shiroko_chibi.py`:手写 SVG 生成 6 个 Q 版白子(wave/cheer/think/run/doze/peek),
3.8KB/张,造型逐项对齐用户立绘(灰发蘑菇头 + 圆钝狼耳 + 倾斜光环 + 青围巾 + 深蓝外套 + 灰裙 + 青黑球鞋)。
**生成模型这条路走不通**:`background: transparent` 不生效、无视觉反馈时无法判断好坏;
代码画路径反而可控、体积小、任意尺寸不糊。
- ⚠️ **cover 必然裁掉贴边插画**:1280×1400 视口下,2400×1350 的背景图只露出中部 1234px,
右下立绘被裁成 38px 一条。→ base.css 新增装饰层令牌 `--tb-deco / --tb-deco-size / --tb-deco-position`
(固定像素 + 四角定位 + fixed,不随视口比例变化)。
- ⚠️ **多背景层的值个数必须恒定**:deco 先写成 4 个值,background-image 变 6 层而 size/position 只有 3 个,
CSS 循环取值 → 整页重复小人。改成固定 6 个槽位(1 图案 + 6 装饰 + 1 背景 = 8 层),
主题必须写满 6 个(缺的填 none),这条写进了 `_scaffold.css`。
- 删掉 `bg-desktop.webp` / `bg-mobile.webp` / `hero.webp` / `dev/gen_shiroko_bg.py`;
整页底色改成纯 CSS 渐变(不用图片,也就没有 cover 缩放差异)。
- 深色关掉 deco(浅色线稿在深色底上糊成灰块);pattern 里的爪印换成 Q 版头像,
opacity 0.42→0.2、tile 240→320(原来铺得太密,像壁纸)。
- 验证:默认主题背景仍 `#f7f8fa`、顶栏纯白、卡片纯白(零回归);箱主页与 `/user` 后台四角装饰都不压内容;
移动端只留立绘 + peek。
## shiroko 插画二次返工:手绘 SVG → 参考立绘图生图(v=20261019l)
用户反馈上次的简笔画"太抽象"。根因判断:用户的立绘是**平涂无描边色块风**,
我用手绘线稿去配它,风格本身就冲突,而且没有真人手感,形准也做不到。
- 新方案:ImageGen **图生图**(`image1` = `static/images/shiroko-cutout.png`,`input_fidelity: high`),
提示词统一为「same character, same art style... flat vector illustration, no outlines」+ 换动作句,
生成 4 个姿态(wave / cheer / think / doze),1024×1024、纯白背景。
**四张角色特征与画风一致**,这一步是手绘做不到的。
- 抠图:四角 + 四边中点 8 点 floodfill(容差 16)+ alpha 1px MinFilter 收边,裁 bbox 后统一高 620、
WebP q90。实测无白边,光环中间的孔也正确透出。
- 删除 `dev/gen_shiroko_chibi.py` 与 6 个手绘 SVG;`chibi/*.webp` 4 张共 106KB。
图案里的手绘小头像也删了(和位图人物并排显得最抽象),改成雪花 + 光环 + 圆环 + 点簇。
- ⚠️ **deco 的 `center` 定位在矮视口会撞车**:右下立绘 520px 高,1366×768 下从 y=248 一直到底,
托腮放 `right center` 会直接站在立绘头顶。百分比是「(视口-图高)×pct」,改用 `top 46%` 并挪到左侧中部。
验证视口:1366×768 / 1440×900 / 1280×1400 三档,人物互不重叠。
- docs 补了完整提示词骨架、动作句与抠图参数,说明"必须平涂无描边"这条经验。
## shiroko 布局重排 + 新坐姿(v=20261019m)
用户反馈:左右不协调、最上边的头被顶栏挡住。
- 布局改为**左右各两个**(左上 wave / 左下 sit,右上 cheer / 右下立绘),
`≥920px` 真实视口才补中间两个(think / doze),矮屏不放(否则压到立绘)。
- 顶部定位从百分比改成**固定 `top 96px`**:顶栏实际高 60px(不是 80),
百分比是「(视口-图高)×pct」,小视口下算出来的值会落进顶栏里。
`bottom 0` 也改成 `bottom 6px`,避免脚贴页底。
- 新增坐姿(盘腿),ImageGen 图生图第 2 次才可用:第 1 张脚被画到画布底边(底部 78 像素贴边),
提示词补 `centered composition, whole body fully visible with generous empty margin` 后正常。
- ⚠️ **Chrome headless 的 `--window-size` ≠ CSS 视口**:实测 1080 → innerHeight 985、768 → 673
(差约 95px 的窗口边框)。所以 `@media (min-height: 1050px)` 在 1080p 下永远不触发。
判断媒体查询门槛要按「真实视口」写,截图验证必须加 `--force-device-scale-factor=1`,
否则系统 DPI 缩放会让截图尺寸和 window-size 也对不上(曾出现 1920 请求出 1088 宽)。
- git:仓库可用(origin = https://git.shiroko.one/tamakyi/tamabox,main),
已本地提交 `0c39aff`,但 `credential.helper=store` 里的凭据过期 → `Failed to authenticate user`。
打包 `../TamaBox-v4-mainline-20261019-2037.tar.gz`(1.5M,含图片素材)供直接部署。
### git push 补推
上次的 `Failed to authenticate user` 是临时态 —— 用户本机 Git for Windows 认证过之后 store 凭据恢复,
本轮 `git push origin main` 直接成功(`38cd41a..0c39aff`),`git rev-list --left-right --count origin/main...HEAD` = 0 0。
远端 8 个 shiroko 素材文件齐全。教训:**不要用一次 push 失败就断言无凭据**,先看一眼提交是否已存在、再试一次。
### 部署后「设置页找不到 shiroko」
线上实测:`/static/css/themes/shiroko.css?v=20261019m` 200 / 5038B 且与本地逐字节一致,
说明二进制确实带了新主题;但 `/user/profile` 渲染出来的主题单选只有 `""`、`default`、`sakura`
→ **根因是 `[theme] available` 白名单没登记 shiroko**(列表在 `theme.load()` 里按配置构建,启动时读一次)。
- `internal/theme/theme.go` 新增 `reportRegistry()` / `discover()`:启动打 info「主题注册表已加载 enabled=...」,
并把「css 在、但没登记」的主题以 warning 打出建议的 available 值。
没有这段时,这个故障在日志里完全没有痕迹,只能靠猜。
`discover()` 用 `fs.Glob(static.FS, "css/themes/*.css")`,用 slugRe 过滤掉 `_scaffold.css`(下划线开头)。
- `conf/app.sample.ini`:`available = default,sakura,shiroko` + 补充「没登记就不显示」的注释。
- `docs/theme-shiroko.md` 补「部署后看不到主题」两段排查(先 curl css 看 200/404,再看 available)。
- 排查口径:**先分「文件没发布」与「没登记」两类**,别一上来怀疑缓存;`StaticVersion` 只影响缓存,不影响列表。
### shiroko 装饰返工:散射 → 左下角一组(v=20261019n)
用户反馈「主题正常了,但展示方式怪怪的」。实测线上 1512×982 截图确认三处:
1. 四角各一个人 = 贴纸墙纸(人物之间、人物与内容都没有关系);
2. 半透明立绘(16%)叠在实心人物旁边 → 虚实混搭,像重影;
3. 右下角人物和右对齐的页脚文案(社区规则 / 备案号 / 存活时长)挤在一起。
改法:只留左下角一组(sit 330 + wave 190,`left 0/bottom 0`),删掉 hero-fade,
纹样减到雪花 + 光环 + 两点(opacity .16、tile 340),卡片补 1px 冷调描边(box-shadow 不占布局)。
990px 以下缩小左移,900px 以下(卡片占满宽)不出人物。
⚠️ **`background-position` 的四值语法必须以边关键字开头**:写成 `max(8px, calc(50% - 555px)) bottom 0`
整条声明作废并回落到左上角(表现为人物跑到顶上、背景出现错位色带)。
要贴卡片边只能 `left max(8px, calc(50% - 555px)) bottom 0`。
### 纹样横向条纹(v=20261019o)
图案元素原先全在 tile 上半部(雪花 y=52 / 光环 62 / 点 150、176),铺开后在整页形成
**横向条纹带**(左栏 60px 带宽均值 245→237→249,跳变 12),这是"看着怪"的第二个来源。
改成 3 行均布(52 / 132 / 152-212)后收敛为单调 245→236。
⚠️ 图片不走 `StaticVersion` 缓存键(`Cache-Control: max-age=86400`),换图案要自己给 url 加 `?v=`。
### 定稿:半隐在卡片后的单人(v=20261019p)
用户从 A/B/C/D 四版里选了 D(半隐在卡片后),并追加两条:人物离开底边一点、纹样再淡一点。
- `sit` 单人,`left max(6px, calc(50% - 515px)) bottom 26px`;宽度 177px,右缘压进卡片 40px。
- **离底边后必须配接触阴影**:一层 `radial-gradient(ellipse, rgba(63,96,138,.22), transparent 72%)`
128×15px,垫在脚下一格(deco 第 2 个槽位,绘制顺序在人物之下)。没有它,抬高 26px 会读成悬空。
- 纹样 opacity 0.13→0.085(深色 0.09→0.055);`?v=20261019p` 同步改。
- 剩余素材(wave/cheer/think/doze)保留但不上页面,换姿态只改 `--tb-deco` 的 url。
### shiroko 按二次元官网重做:整页场景 + 悬浮磨砂 UI(v=20261019q)
用户反馈「还是很怪,去网上学一下布局」。实际去看站:
- 蔚蓝档案 JP 首页(bluearchive.jp,截图存 outputs/ref/bluearchive_jp_.png):**整屏一张场景插画,
logo/按钮/横幅浮在画面上**,没有"白卡片"这层东西;
- marshmallow-qa / peing 这类真正的提问箱服务被 Cloudflare 拦,拿不到参照。
结论:前几版都卡在"白底 + 留白摆人物"这个结构上,人物没有环境,怎么调位置都像贴纸。
改法:生成教室场景(ImageGen,1536×1024)→ 裁掉底部 160px 水印 → 混白 38% → 模糊 1.4px →
WebP 1920×1080(22KB)/ 720×1003 竖版(6KB);卡片与顶栏改 `rgba(255,255,255,.86)` +
`backdrop-filter: blur(16px)`;人物 340px 站在左侧地面、离底 36px + 椭圆接触阴影;浅色不再叠纹样。
深色仍用渐变 + pattern-dark(白天教室压在深底上会脏)。
⚠️ **`ruleBody` 必须剥 CSS 注释**:令牌前写一行注释时,`tokenValue` 按 ";" 切完再按 ":" 取键,
会拿到 `/* ... */ --tb-surface`,色块预览静默变空、`TestThemeCSSComplete` 报"缺少令牌"。
已加 `commentRe` + `TestRuleBodyIgnoresComments`。
⚠️ ImageGen 出的图右下角有 `AI生成 WORKBUDDY` 水印,用作整页背景必须裁掉。
## 23:40 人像换成「窗边背影」
- 用户否掉了盘腿坐的正面人像("和背景没那么突兀的")。根因:正面直视 + 无环境 + 饱和度高,
在压白 38% 的空教室里就是一块贴纸。
- 新素材 `chibi/window.webp`(135×460, 13KB):文生图出侧后方 3/4 视角(双手背在身后)→ 再
image-to-image 转"纯色块无描边 + 去窗框"→ 抠白底 → **RGB 压白 16%** → 高 460 / q86。
第一步文生图会把窗框画进背景,抠不干净,必须靠第二步重绘。
- CSS:`--tb-deco` 首槽换 window.webp,高 380→400px,接触阴影 96×13 复位到脚底;`StaticVersion` → `20261019r`
(图片另带 `?v=20261019r`)。删除 `sit.webp`;`docs/theme-shiroko.md` 补两步做法与摆位说明。
- 提交 `e95671f` 已推送。教训:**人像与场景的"雾度"要对齐**——场景压白多少,人像也压相当比例。