Skip to content

视觉层级细化 —— 外部提案第三轮的采纳项实施方案

物理位置:~/reading-browser/docs/plans/visual-hierarchy-refinement-plan.md 创建:2026-08-13 · 状态:✅ T1-T7 全部完成(2026-08-14);T8 转交 backlog §2.2 纯 RB 前端 · 零 migration / 零 Supabase / 零 RVH(唯一例外见 T8 的「不做」说明)

2026-08-13 实机探针更新:D1 / D6 已在 dev app 上验完(方法见 §6),结论并入 T2 / T7; T6 的设计被实机数据推翻并重写(原「取前 N 条」会保留废义项、藏掉有用的), D5 随之改写。D2 / D3 已于同日拍板(见 T4;P8 出处也考古到了,原计划「出处失传」一句已订正)。 D5 亦已定——但方向与我中途的改写相反:查库后发现那次改写是从单个例子过度概括, 已改回「取前 N 条 + 交叉引用降权」,订正表在 T6。 至此 D1-D6 全部拍板完毕,无待决项(D4 不阻塞,写代码时顺手看暗色)。

实施完成(2026-08-14):T1-T3 9b54d59 · T4 8cfcf1d · T5 6ae1789 · T7 caa398d + 97be96b(用户实机反馈后改三带配色)· T6 6ac23dfT7 有一次用户驱动的返工,值得记:首版沿用 CEFR_PALETTE.hex 的 6 色,用户实机指出 「C1 和 B2 区分度不够」;量化后发现 ①C1/C2 比 C1/B2 更挤(ΔE 10.7 vs 12.6) ②根因是那套值按「白底对比度单调递增」而非「互相可分辨」选的 ③暗色方向反了 (「越难越暗」在暗底上是越难越不突出,我首版没写 dark: 变体)。改三带后跨带最小 ΔE 34.4(3 倍)。→ 教训:颜色方案不要只在亮色下自测,本计划 §1 那句 「实机必看亮/暗双主题」当时没做到,是用户截图补上的。

0. 这份计划是什么 / 新会话从这里开始

来源 = ~/Downloads/readbrowser-visual-optimization-proposal.md + 4 个 mockup HTML (另一 LLM 看截图给的视觉优化建议)。逐条裁定已完成,留痕在 backlog.md §外部 backlog 第三轮——约 24 条建议里 6 条采纳、5 条改方案后采纳、 4 条事实错误(描述的是已实现状态)、3 条与既有决策冲突、1 条数据形态不支持。

🔒 新会话不要重新论证已裁定的条目,尤其不要重做下面这些已明确否决的:

否决项一句话理由(详见 backlog 第三轮表)
发现页站点两列改单列HomePage.tsx:698-701 写死的对照设计冲突;首屏信息量减半
星标改品牌绿 / 移到行首SEMANTIC.bookmark(amber) 是全仓收藏语义;行首是 favicon 槽,塞可点星会造误触热区
「我的记忆」用新 --memory 暖色 tokenamber 已有 5 个语义且两个与之同屏;仓内已有个性化语言 = primary 左竖条
「待你行动的提醒」品牌绿 token 家族三个落点全部不成立 → 建了就是死 token
本周主题标签分类色板report.themes 是 LLM 自由文本非枚举,无稳定类别集可映射
数据稀疏降级组件已实现(roadmap 2-4 / StatsInsights + StatsPanel 四个 gate)
picker 数字权重降低已是现状ReviewPickerRow 用 10px 次要灰)
review landing 中央补空态我方误判,实机证伪:中央由 rb-cache://…/__review_empty__ 有意渲染

已完成(不在本计划范围,仅供上下文):T0「回顾 picker 去待办感」= commit 7028a0d (起手行变行动 / 计数带单位词 / 靶心换 Sparkles / 会话内进度改累计)。它不属于原提案 9 条, 是用户追问「全部 · 221」后新长出来的。

本计划 = 剩余 8 条(T1-T8)的实施方案。


1. 贯穿约束(每条都适用,不再逐条重复)

#约束出处
C1组件内禁 hex 字面量,颜色一律走 lib/design-tokens.ts红线 #8 / arch-check H1
C2禁 Tailwind 语义色硬编码(bg-amber-500 等),走 SEMANTIC.*arch-check S12
C3禁非 scale spacing / 任意 rounded-[…] / text-[Npx] / shadow-[…]arch-check S11/S14/S15/S16
C4JSX 禁内联中英文,一律走 src/lib/strings/facade + zh + en 三处逐 key 对齐arch-check S19/S22/S23
C5列表行走 ui/ListItem(或其 wrapper),空态走 ui/EmptyStateui-standards §13 / ui-check U1
C6<button> 需同行 // arch-r1: 注释豁免arch-check S10
C7图标走 lucide + ui/Icon(强制 ICON_SIZE scale),禁散落 <svg>arch-check S17

每条任务完成后的验证链(固定不变,后文不再重复):

pnpm exec tsc --noEmit
pnpm run check:no-inline-zh && pnpm run check:no-inline-en && pnpm run audit:locale
pnpm exec vitest run
pnpm build
/arch-check  →  /ui-check

再加实机pnpm tauri dev + rb-debug MCP:rb_snapshot / rb_dom / rb_click)。 ⚠️ 实机必看亮/暗双主题——本轮多条涉及淡填充,mockup 全是亮色单主题,暗底要另看。


2. 批次与依赖

批 1(无依赖,可并行,约 0.5d)   T1 → T2 → T3   (同一屏,建议连做后一起看效果)
                                  T4              (发现页,独立;D2/D3 已定,可做)
批 2(需先定 token,约 0.5d)      T5
批 3                              T7(D6 已定,可直接做)
                                  T6(D5 已定,可直接做)
不做(转交既有条目)                T8

批次已随决策推进变化(2026-08-13):

  • T7 原本和 T6 一起压在「需实机再定」,D6 已定 → 可直接做
  • T4 原阻塞于 D2/D3,两条已拍板 → 可直接做
  • T6 原阻塞于 D5,已定 → 可直接做
  • T1-T7 现已全部无阻塞;D4(personal 暗色观感)写代码时顺手看即可

T1→T2→T3 必须连做再验收:三条都在词汇详情页同一屏改视觉权重,单改任何一条都看不出 「层次是否真的分开了」。T4 独立,可先可后。


3. 任务详述

T1 —「我的记忆」上移到词源/同族词之前

现状src/components/vocab/WordDetailView.tsx):

:60-75  <div className="pt-4 mb-4">        ← 词头块
          插图 + 词 + IPA + 音频
:70        <EtymologyAction … onWhite />   ← 词源/同族词(在词头块**内部**)
        </div>
:79     <WordMemorySection word={word} />  ← 我的记忆
:82+    释义(PosGroupBlock)

改法:把 <EtymologyAction> 移出词头块,放到 <WordMemorySection> 之后。 结果顺序 = 词头 → 我的记忆 → 词源/同族词 → 释义。

注意

  • EtymologyAction 的组件注释写着「贴词头渲染、不进 PosGroupBlock」——移位后这句话失真, 必须同步改注释,否则下一个人会把它当 bug 移回去。
  • WordMemorySection 无数据时 return null(预装词/纯 Browse 词常见),此时视觉退回 「词头 → 词源 → 释义」,与今天一致 → 无回归。
  • 传给 WordMemorySection 的是 PK word 而非 displayWord别顺手改(记忆表按归一 PK 存)。

验收:Notebook tab 选一个有记忆数据的词(如实机库里的 admiral/appoint), 「我的记忆」在词源之上;再选一个预装词确认整块隐藏且布局不塌。


T2 — 词源/同族词收起态去壳

现状src/components/vocab/EtymologyAction.tsx:39-43):cardClassrounded-lg border …bg-surface-muted加在外层 div 上 → 收起态也是两张竖排的卡。

改法:收起态只留「chevron + 图标 + 标签」的文字行,展开后才出现卡。 目标形态(对齐 mockup-01):两个标签横排一行,展开体在其下方成卡。

✅ 决策点 D1 —— 已定:采用横排(2026-08-13 实机探针,见 §6)

EtymologyAction3 个调用点——WordDetailView.tsx:70(Library 详情)+ ReviewCardFace.tsx:224 + ClozeRevealCard.tsx:284(两处复习卡,都是 onWhite), 改布局三处一起变,所以窄侧栏是本条唯一的风险面。判定依据:

  • Library(宽)实图:两标签收成一行 + 一条发丝线,权重明显降下来,比原先两张竖排卡干净得多。
  • 意外收益:横排 + 下边框读起来像一组「二选一」标签,而这正是它的真实语义 (open 是互斥单状态)——affordance 与行为对上了,不是巧合式好看。
  • 复习卡(320px)判定 = 测量,不是实图(诚实标注):收起态整行实测约 108 CSS px (Library 实图上量),侧栏最窄 320px → 约 3 倍余量,不可能挤。 展开态本条不动(只改收起态的头),grid-cols-2 + truncate 既有行为不变,不引入新风险。

⚠️ 为什么没拿到复习卡实图:抽到的卡是 monkey,其 etymology / word_family 双 NULL → 组件直接 return null。要翻到有词源的词(如 observe)得连续给真实卡片评分, 会改动真实 SM-2 排期,为看视觉不值当。 → 落地时补一眼:第一张带词源的复习卡出现时确认一次即可(零成本)。

陷阱

  • sectionHeader 里的按钮有 e.stopPropagation()——保留(复习卡上点它会触发翻面/收起)。
  • open 是二选一互斥单状态,横排后仍只展开一张卡,别改成可同时展开。

验收:Library 详情 + 复习卡揭晓面(两处)收起态无卡壳、展开态成卡、互斥仍生效、 点击不触发复习卡翻面。


T3 — AI 例句徽标:文字 → 图标

现状src/components/vocab/WordSenses.tsx:245-252):每条 LLM 例句后跟一个文字徽标 AISEMANTIC.aiBadge = purple 底 chip)。义项多的词(admiral 5 义项)会重复 5-8 次。

改法:保留逐句标注(它是溯源信息,改成区域级会丢「哪一句是 AI 生成」的诚实性), 把文字 chip 换成 12px Sparkles 图标 + title 提示。

要动的东西

  1. design-tokens.ts 加一个纯文字色 token(现有 SEMANTIC.aiBadge 是 bg+text 的 chip 串, 图标用不上它)。建议 SEMANTIC.aiMark: 'text-purple-600 dark:text-purple-300'—— 紫色家族沿用(与 typeBadge.analysis 同谱),保持「AI 生成」在全仓的色彩一致性。
  2. WordSenses.tsx 换渲染:<Icon icon={Sparkles} size="xs" className={...} title={S.detail.aiExampleTitle} /> (走 ui/Icon 满足 C7;title 保留供悬停解释)。
  3. S.detail.aiExample(字面量 'AI')改完即无引用 → 从 facade + zh + en 三处删除 (已核实全仓仅 WordSenses.tsx:250 一处用它)。aiExampleTitle 保留。

无障碍:图标需可感知——Icon 上给 title 不够时补 aria-label,别只留一个纯装饰图标。

验收:admiral 之类多义项词的 AI 例句仍逐句可辨、整屏紫色噪点明显减少; audit:locale 仍 0 suspect(删 key 要三边同删)。


T4 — 发现页站点/订阅:TileListItem

现状:同一个发现页里存在两套列表语法——

区块用什么位置
继续阅读 / 最近打开ListItem variant="double" density="normal"HomePage.tsx:503+ / :567+
我的收藏站点SiteCardTile tone="bordered":712 / 定义 :943
推荐站点Tile tone="bordered":759
我的订阅Tile tone="bordered":813
推荐订阅Tile tone="bordered":872

改法:后四块迁到 ListItem variant="double" density="normal",与前两块统一。 ListItem 的文档注释里 double 的实体映射原文就写着 「leading + 2-line stack (history / sites / RSS / feeds)」——迁移方向与既有实体映射一致, 不是新发明。

槽位映射:Tile.leading → ListItem.leadingtitle → titlesubtitle → subtitleaction → trailing(action 的 loading/tone 要自己在 trailing 里用 IconButton 复原)。

✅ 决策点 D2 —— 已定:删除 Tile(2026-08-13 用户拍板) Tile 全仓 4 个调用点,全在 HomePage(3 直接 + 1 经 SiteCard),四块全迁走后零引用。 删 src/components/ui/Tile.tsx + ui/index.ts barrel 导出 + lib/ui-variants.tstileVariants,并同步更新两处组件表docs/ui-component-inventory.mdCLAUDE.md §2(两处都点名列了 Tile)。依据 = 本仓对死代码的既有态度 (phrase_interaction_log v20 建 v25 删的教训)。

✅ 决策点 D3 —— 已定:整行可点 + 星标 hover 显现(2026-08-13 用户拍板)

🔴 原计划写的「P8 出处失传」是错的,此处订正git log -S "P8" -- src/components/ui/Tile.tsx → commit b9772d5(2026-07-18), message 里那批反馈第 8 条原文:「Home:隐藏前进后退刷新按钮组、Tile 仅标题可点/hover、 收藏存网站名…」。而改动前的代码注释是 Without action: whole Tile is one <button> —— 即整卡可点是旧行为,P8 是用户当时明确要求改掉它。这不是无主决定。

为什么现在可以改回来:P8 约束的对象是 Tile(2 列描边大卡,整卡点击是又大又含糊的靶)。 换成扁平列表行后前提变了,且同一实体已有现成模型 —— MySitesPanel.tsx:139 的收藏站点行本来就是 ListItem variant="double" + 整行 onClick + 星标 opacity-0 group-hover:opacity-100, 而那个面板正是发现页「Browse All」点进去的地方。迁移 = 发现页收敛到同一实体已有的模型, 不是把 P8 推翻进真空。

🔒 仍须写进 CHANGELOG:这是对 2026-07-18 P8 的有意反转,不许当无声副作用。

⚠️ 一处刻意的不对称(实施时照此做,不是疏漏):星标 hover 显现只用于「已收藏」两栏

栏目星标语义显示
我的收藏站点 / 我的订阅取消收藏opacity-0 group-hover:opacity-100(同 MySitesPanel,零损失)
推荐站点 / 推荐订阅收藏常显

理由:推荐两栏里星标是该栏存在的主要动作,hover 隐藏等于把整栏的用途藏起来。 MySitesPanel 没这个问题——它每一行都已收藏,星标只承担「取消」。

陷阱

  • SiteCard 的副标题去重护栏(name === domain 时不渲染 subtitle)要一起带走,别丢。
  • 星标 action 的 tone: 'bookmark' | 'rss' hover 色在 Tile.tsxACTION_HOVER_CLS写的是裸 Tailwind 色hover:text-amber-500 等);迁到 IconButton 时改用 SEMANTIC.bookmark.iconHover / SEMANTIC.rss.brand.textHover,否则把 S12 违规搬到新家。
  • 四块外层的 grid-cols-1 md:grid-cols-2 不动(两列对照是已裁定保留的设计)。
  • hover 显现的行需要 className="group"(MySitesPanel 就是这么挂的),漏了星标永不出现。

验收

  • 发现页四块与同页「最近打开」行高/字号/分隔线一致;两列布局不变
  • 整行可点;星标点击触发收藏/取消,不冒泡打开站点(stopPropagation
  • 「我的」两栏星标 hover 才现,「推荐」两栏星标常显
  • rg '<Tile|tileVariants' src/ 归零;ui-component-inventory.mdCLAUDE.md §2 已去掉 Tile
  • /ui-check U1 不新增违规

T5 — SEMANTIC.personal:个性化内容的统一视觉身份

问题:AI 为你生成/挑选的内容目前和通用参考资料同质。而仓内已有两种不同的写法:

位置现在长什么样
WordMemorySection.tsx:107(我的记忆)rounded-lg bg-surface-muted p-3(灰实心块)
WeeklyReportCard.tsx:171(下一步读什么)border-l-2 border-primary/40 pl-3(primary 左竖条)

改法:以后者为统一目标(primary 左竖条),新增 token 后两处共同消费。 不新增暖色——理由见 §0 否决表。

建议配方(design-tokens.ts):

ts
// 「为你生成/挑选的内容」的统一身份(我的记忆 / 下一步读什么)。
// 用 primary 而非新暖色:amber 已有 5 个语义且其中两个与「我的记忆」同屏;
// 且 primary 在仓内已隐含「你的东西」(收藏、在学词 chips)。
personal: {
  container: 'bg-primary/5 border-l-2 border-primary/40 rounded-r-lg',
  label:     'text-primary',
},

🔴 决策点 D4:primary = #1f4e3d(亮)/ #7fb590(暗)。bg-primary/5暗色下 是浅绿薄雾压在 #3a3a3a 上,必须实机看——太脏就退成只留左竖条、不铺底。

陷阱

  • 提案说「三处统一」,第三处是发现页「这周的你」——那张卡还不存在(backlog §2.2 未启动)。 本条只铺 2 处,token 注释里写清第三处的预留,别去造那张卡。
  • Sparkles 图标两处已经都有,不用新增。

验收:两处视觉同族;亮/暗双主题都不脏;/arch-check S12 无新增。


T6 — Library 义项折叠(默认 N 条 + 展开其余)

现状WordDetailView.tsx:88-90 对每个 entries(POS 组)渲染 PosGroupBlock, 组内 group.senses 全量展开。

这条的价值比原估计更大(2026-08-13 实测本地库 221 个有释义的生词本词):

义项数分布                            平均 6.8 条 / 最多 20 条
  1-3 条(折叠无意义)      37 词
  4-6 条                    75 词
  7-10 条                   85 词
  11 条以上                 24 词
→ 83%(184/221)的词有 4 条以上义项。admiral 的 5 条其实低于平均。

复习侧已有现成范式可抄ReviewCardFace.tsx:140-172):折叠态构造一个裁剪过的 collapsedGroup,展开开关是 ReviewCardFace 自己的 showAll local state + 一个 arch-r1 豁免的裸 <button>(chevron + 文案)。折叠逻辑在调用方,不在 PosGroupBlock ——沿用这个分工,别往 PosGroupBlock 里加折叠状态。

✅ 决策点 D5 —— 已定:取前 N 条(N=3,per-POS)+ 交叉引用义项降权(2026-08-13)

🔴 本条曾被我改成「hero = 语境命中义项」,2026-08-13 查数据后改回来了。订正记录如下, 别再往回改(这是本计划里唯一一处「结论翻烧饼」,写全是为了让它到此为止):

我当时的论证实测结论
「库里 sense 顺序不代表价值」——admiral 义项 1 是废话却排第一生词本 1512 条义项里,交叉引用类(Alternative … form of / Plural of / Synonym of 等非真实词义)共 19 条排在第 1 位的只有 2 条过度概括:从 admiral 一个例子推出了普遍规律,实际是 221 词里的 2 个 outlier
「用 sense_gloss 当 hero,产品已经知道哪个义项对用户有意义」生词本 222 词里能拿到 sense_gloss 的只有 15 个(6.8%);有语境句的词总共才 16 个覆盖率是零头:93% 的词仍要走前 N 条,为 7% 做重构不划算

不把 getWordMemory 提到 WordDetailView。cloze 语境是 v16(2026-06-11) 才有的能力、 老词全没有;等覆盖率真上来了它自然会变成值得做的改动,那时再说

改法

  1. per-POS 取前 3 条,其余收进「展开其余 {count} 个义项」。粒度是 per-POS 而非全词—— POS 是语义边界,跨 POS 截断会整个藏掉一个词性。
  2. 交叉引用义项降权(覆盖上面那 2 个 outlier,成本≈一个正则):gloss 命中 ^(Alternative .*form of|Alternative spelling|Obsolete .*form of|Misspelling of|Synonym of|Plural of|Archaic .*form of) 的义项排到可见 N 之外(不是删除,展开后照常显示)。 ⚠️ 只降权、不重排其余义项——除这类前缀外没有可信的价值信号,多排一步就是瞎猜。

为什么不用提案给的 opacity 弱化:① opacity-60WordMemorySection 的 SourceRow 已表示「不可点」,撞语义;② 对比度下降;③ 折叠是信息量守恒的降权,opacity 是猜测式降权。

文案:需要新 key(Library 属 vocab namespace,不要跨 namespace 借用 focusReview 的 showAllDefs)。措辞与复习侧保持一致:「展开其余 {count} 个义项」/「收起其余释义」。

验收:admiral 默认露出义项 2/3/4(第 1 条因交叉引用降权被推后);展开/收起不抖动; ≤3 条义项的词不显示按钮;多 POS 的词每个词性各自折叠。


T7 — CEFR 色点:点 + 字母合体

现状src/components/VocabPanel.tsx:129-136):CefrDot = w-1.5 h-1.56px)纯色圆点, 色值走 cefrDotClassbgDark,如 bg-teal-600)。注释写着它「替代原右侧 CEFR badge」—— 字母那一路已经撤了。

问题不是「点太小」而是「六个相邻冷色相」:2026-08-12 改冷色系后是 teal→cyan→sky→blue→indigo→violet(design-tokens.ts:25-50),6px 圆点上肉眼无法分辨, 加大到 8px 也救不回来。CEFR 是有序量,颜色只能编码「大概位置」,字母才是可靠通道。

✅ 决策点 D6 —— 已定:w-4 h-4 实心圆 + 白字母(2026-08-13 实机双探针,见 §6)

  1. 「放不放得下」已不是问题:探针前后两屏行 y 坐标完全一致(327/373/420/466/513…), 16px 圆塞进 density="dense"行高零变化。原计划这条顾虑可以划掉。
  2. 根因已被数据坐实:基线 16 行的点肉眼全同色,查库确认它们分属 C1×3 + B2×13indigo-600 vs blue-600)。全库分布 A1 100 / A2 54 / B1 44 / B2 19 / C1 3 —— 档位差是真的,看不出来纯是「6px + 相邻冷色相」的问题。
  3. 浅底方案(探针 B,复用 cefrBadgeClass)已否决:更克制、对比度合规,但六个 -100 浅底几乎同色,颜色通道基本失效;更要命的是它违背调色板自身的设计规则—— design-tokens.ts:10-13 写明「序信号 = 与背景的对比度」(A1 安静 → C2 突出), 全用 -100 会把这条序信号抹平。

🔴 实现细节(实机才发现,务必照做)填充色不能用 cefrDotClass 它返回 bgDarkbg-teal-600 / bg-cyan-600 / …),白字压上去 A1/A2 只有约 3.1:1, 9px 小字达不到 WCAG AA 的 4.5。而 CEFR_PALETTE.hex 那一档 (teal-600 / cyan-700 / sky-700 / blue-700 / indigo-700 / violet-800)正是注释里标注 「其上压白字」可读的值。→ 字母版需要一个配 hex 档的 class getter(如 cefrSolidClass), 不要复用 cefrDotClass。 ⚠️ 即便如此 A1(teal-600 #0d9488)白字仍约 3.06:1,是六档里唯一不达标的 → 单独决定是否降到 teal-700,或给字母加深色描边。这条别跳过。

陷阱

  • 未知/缺失档走 cefrDotClass 的中性灰(不是 unknown 的琥珀),这条语义要保住—— 合体后无字母可写,用空圆或 ,别退回黄色(会被读成某一难度档)。
  • VocabPanel.tsx:324-325 有一句陈旧注释「CEFR 点已有绿/黄/橙/红」——那是暖色时代的遗留, 现在是冷色系。顺手改掉。
  • 影响 Library 三个 tab(生词本/浏览/已认识)全部列表行,改一处三处都变。

验收:一眼能扫出难度分布(而不是「一排都是蓝的」);行高不变;三个 tab 一致。


T8 —「下一步读什么」可点击 —— 本计划不做

提案要求给周报的「下一步读什么」加「去读这篇」按钮。做不到,且不属于视觉层: report.rs:54ReadingReport.next_step 是纯 String(无 id/url), report.rs:213 喂 LLM 的候选连 URL 都没传。要改四处 + /edge-deploy, 且有三个前置条件(候选池换 recommended_articles / 发现页只读缓存不触发生成 / 与 TriageActionCard 的槽位互斥规则)。

转交 backlog.md §🟡 2.2。视觉部分已随 T5 落地(next_step 块消费 SEMANTIC.personal)。

🔴 2026-08-14 复评:那颗按钮已裁定不做(不只是"做不到")。查代码后发现两个入口的 候选池根本不同源——发现页用 recommended_articles(有 url/CEFR/正文/在学词,可点), 周报用本地未读 RSS(只有 title + feed 名,无 url),是质量倒挂:叙事上更郑重的那个反而 是降级版,还可能点名一篇发现页里没有的文章(跨池不可达)。而换池虽解决空池、却制造重复。 根子是定位:周报是反思层,"下一步读什么"是前瞻,本不该在这里长第二个推荐源。 改走「一句过渡导流到发现页」,把两个入口从竞争改成串联。完整推导 + 三条时间口径不一致的 独立缺陷,见 §2.2〈2026-08-14 复评〉。


4. 收尾(全部做完后)

  • [ ] CHANGELOG.md [Unreleased] 补一条(叙事粒度,参照本仓既有条目风格)
  • [ ] backlog.md 第三轮那节的「✅ 已落地」子节追加本轮结果,并把表里对应行的状态更新
  • [ ] 删 Tile 后更新 docs/ui-component-inventory.md + CLAUDE.md §2 UI 组件表(D2 已定)
  • [ ] CHANGELOG 里显式写明「整行可点」是对 2026-07-18 P8 的有意反转及其理由(D3)
  • [ ] /doc-sync-check 兜底(本计划预期无强制项——不碰 migrations / commands / stores / 依赖)

5. 决策点状态

#问题状态
D1词源/同族词收起态横排还是竖排?已定:横排(实机,见 T2 / §6)。落地时补看一眼带词源的复习卡
D2Tile 迁空后删不删?已定:删 + 同步 ui-component-inventory.mdCLAUDE.md §2
D3恢复「整行可点」可接受吗?已定:整行可点 + 星标 hover 显现(对齐 MySitesPanel)。P8 出处已考古到b9772d5,是用户 2026-07-18 的明确要求)→ 属有意反转,必须进 CHANGELOG。附一条刻意的不对称:推荐两栏星标常显,见 T4
D4SEMANTIC.personal 配方(bg-primary/5 暗色下脏不脏)待实机(写代码时顺手看,不必单独跑一轮)
D5义项折叠取哪几条?已定:per-POS 前 3 条 + 交叉引用义项降权。中途曾改成「hero=语境义项」,查库后改回sense_gloss 覆盖率仅 6.8%、"废义项排第一" 221 词里只 2 例)→ 不提 getWordMemory。订正表见 T6
D6CEFR 色点形态已定:16px 实心圆 + 白字母(实机双探针)。附带一条必做的对比度修正,见 T7

6. 实机探针记录(2026-08-13)

方法(可复现,成本极低,建议后续拿不准的视觉决策照做): pnpm tauri dev 起 dev app → 直接改源码打临时探针(Vite HMR 秒生效)→ rb_snapshot 截图对比 → git checkout -- <精确路径> 回滚。探针文件在 HEAD 干净时回滚零风险。 ⚠️ 必须用 pathspec,禁 checkout . / reset --hard——工作树里可能有其他会话的未提交改动 (本轮就有,见 CLAUDE.md §10.3 路径 B)。

用到的取证手段

  • rb_snapshot 截图(判视觉 + 靠行 y 坐标判行高有没有变,比肉眼可靠)
  • rb_db_query 查真实数据分布(证明「点看起来一样」是配色问题而非数据碰巧同档)
  • rb_dom 读 outerHTML(确认 class 实际落到了哪、组件有没有 return null)

证据留档

  • D6 基线:生词本前 16 行点全同色 → 查库 = C1×3 + B2×13(indigo-600 vs blue-600
  • D6 探针 A/B:行 y 坐标两屏一致 = 行高不变;浏览 tab 全档位混排下 A 可分辨、B 失效
  • D1:Library admiral 详情横排实图;收起行实测 ≈108 CSS px
  • T3 前提坐实:admiral 5 个义项各挂一个紫 AI chip
  • T6 设计推翻:admiral 义项 1 = 「Alternative letter-case form…」几乎无用却排第一

没能做到的(别在下一轮重试同一条路):拿不到带词源的复习卡实图—— monkey 双 NULL 使组件 return null,翻到 observe 需连续评分 = 改动真实 SM-2 排期。 D1 因此以测量结论收尾。

6.1 D5 的数据取证(dev app 关着也能跑)

⚠️ 一条方法教训:D1/D6 用截图定,是对的(视觉问题);但 T6 我一度也拿一张截图 (admiral 详情)去定分布问题,推出「库里 sense 顺序不代表价值」——查库后证明那是 221 词里的 2 个 outlier。「这一屏看起来如何」不能回答「普遍如何」,后者只能查库。

库路径 ~/Library/Application Support/com.lampio.dev/lampio.db只读查询sqlite3 直连即可, 不需要 dev app 在跑;rb_db_query 则需要)。三条查询与 2026-08-13 的结果:

sql
-- ① sense_gloss 覆盖率 → 222 词里 15 个(6.8%);有语境句的词共 16
SELECT (SELECT COUNT(*) FROM learning_entries WHERE deleted_at IS NULL),
       (SELECT COUNT(DISTINCT le.word) FROM learning_entries le
          JOIN word_cloze_contexts c ON c.word = le.word COLLATE NOCASE
         WHERE le.deleted_at IS NULL AND c.deleted_at IS NULL
           AND c.sense_gloss IS NOT NULL AND c.sense_gloss != '');

-- ② 义项数分布 → 平均 6.8 / 最多 20;83%(184/221) 有 4 条以上  ← T6 值得做的依据
--    注意 pos_definitions 是**按词性 key 的对象**,义项在 `definitions` 数组里(不是 `senses`)
SELECT SUM(json_array_length(json_extract(pd.value,'$.definitions')))
  FROM vocabulary v, json_each(v.pos_definitions) pd WHERE v.word = ?;

-- ③ 交叉引用义项污染 → 1512 条义项里 19 条,排第 1 位的仅 2 条  ← 推翻「顺序不代表价值」
--    用 ROW_NUMBER() OVER (PARTITION BY word ORDER BY pd.key, d.key) 取位次

这三个数不是一次性结论:①会随 cloze 使用增长(v16/2026-06-11 才有的能力), 将来若显著上升,「hero = 语境义项」值得重新评估——届时先重跑 ①,别凭印象