Skip to content

fix(wallet,ui): show only filterable text in the 模型/Key transaction cells - #231

Merged
argszero merged 1 commit into
mainfrom
fix/tx-model-key-cell-filter-parity
Sep 14, 2026
Merged

argszero merged 1 commit into
mainfrom
fix/tx-model-key-cell-filter-parity

Conversation

@argszero

Copy link
Copy Markdown
Owner

Summary

交易表的「模型」列与「Key」列是服务端筛选(tx_where 的 model LIKE ? / COALESCE(...) LIKE ?),但两列的单元格对「库内无值」的行会自己编一个兜底文案:txsToView 渲染 t.model || txType(t.type) 与 t.key_name || t.key_label || (consume||earn ? "—" : txType(t.type)) —— 兜底是本地化类型名(「赠送」/「过期」)。服务端没有任何语言包(全仓没有 Rust 侧文案表),这个字符串在 SQL 里永远匹配不到 ⇒ 用户按屏幕上刚看到的文案筛选得 0 行。

有模型名/Key 名的行一切正常 —— 这正是问题所在:列看起来是可筛选的,所以这是会说谎的筛选,不是缺功能(类型有专门的列与 tab,控件正确)。

不变量是「单元格能显示出来的每一段文字,都必须能被该列筛选命中」。既然惹事的兜底是本地化的,修法就落在显示侧:无值行改显示语言中性的占位符 —(同列族的「用户」列 t.user_name || "—" 与 Key 列自己的 consume/earn 分支早已如此),两侧共用同一条定义:

前端 txsToView 服务端 tx_where
模型 t.model || "—" COALESCE(NULLIF(model, ''), '—')
Key t.key_name || t.key_label || "—" COALESCE(NULLIF(ak.name,''), NULLIF(<key_label 表达式>, ''), '—')

SQL 侧要逐层 NULLIF:JS || 视空串为缺失,而 COALESCE 只对 NULL 回退 —— 与 C2101 修「空名 api_keys.name」时同一个不对称。

导出 CSV 无需改动:它打印同一份视图行的 t.model / t.key,自动跟随单元格(点数 C2054、时间 C2111 已立的「同口径」规则)。

Related Issue

(无关联 issue)

Changes

  • 修复:交易表「模型」/「Key」列的无值兜底改为语言中性占位符 —;服务端两列的 LIKE 表达式搜同一个文案
  • 同步 TxColFilters 字段文档与 ui/README.md(新增「单元格文案 = 筛选口径」一节)
  • ui/index.html cache-bust app.js?v=20260914-7 → -8
  • 涉及配置/数据结构的改动已同步示例文件 —— 不适用(无 schema/配置变更)

Tests

  • cargo test 全部通过(235 → 238,新增 3 个)
  • cargo fmt --check 通过
  • cargo clippy --all-targets 干净
  • 新增单元测试(3 个,拆开是为了让 A/B 读出互不遮蔽的红集合):
    • tx_model_and_key_filters_match_the_displayed_placeholder —— 按单元格显示的文案筛选必须命中该行(修前 0 行)
    • tx_model_filter_still_matches_real_model_names —— 阳性对照:真实模型名照旧命中
    • tx_model_filter_does_not_take_localized_type_labels —— 阴性对照:类型名(「赠送」)不属于该列口径

A/B(两套仪器、五个腿,基线钉在不可变提交 2be0d54;git show 取基线,基线==修复即中止)

  • 前端仪器:jsdom 启真 ui/index.html + 四个真脚本(只 stub fetch 与下载),读渲染后的单元格文本、模型 筛选框发到线上的参数、以及导出的 CSV 字节;11 项检查
  • 服务端仪器:cargo test tx_(3 个测试)
腿 内容 红集
v0_orig 改前两侧(2be0d54) B1 B2 B3 B4 R_main
m_nosql 显示已修、筛选回退 R_main
m_nodisp 筛选已修、显示保留 B1 B2 B3 B4
m_label 显示保留 + 把中文标签硬编码进 SQL(竞争方案) B1 B2 B3 B4 R_main R_neg
live 工作树(修复) 无(14/14)
  • m_nosql 与 m_nodisp 互不相交 ⇒ 每一半都被各自的仪器钉住;A0 内建对照与全部 5 个非轴对照(A1 A2 A3 C1 D1 E1)每腿皆绿
  • 如实记录:m_label 的红集包含 v0_orig(竞争方案连同样的前端检查一起挂,外加阴性对照);它被拒的理由是「本地化文案不能住进 SQL」(服务端无语言包,硬编码中文等于把 i18n 塞进 SQL)—— 该结论由 R_neg 这条阴性对照钉住

Checklist

  • 分支命名符合约定(fix/)
  • Commit message 使用 Conventional Commits 格式
  • 单一职责,改动最小化(4 文件;生产代码仅 2 处兜底 + 2 处 LIKE 表达式)

…ells

The transactions table filters its 模型 and Key columns server-side: `tx_where`
builds `model LIKE ?` and `COALESCE(...) LIKE ?` from the text the user types
into the column's `.th-filter` input. Both cells, however, invented a fallback
for rows that have no stored value: `txsToView` rendered
`t.model || txType(t.type)` and
`t.key_name || t.key_label || (consume||earn ? "—" : txType(t.type))` — a
LOCALIZED type label (赠送 / 过期). The server has no language pack (there is no
Rust-side label table at all), so that string can never match the SQL, and a
user who filters by the text they just saw on screen gets 0 rows. Rows that do
have a stored value were already fine, which is what makes the column look
usable: it is a filter that lies, not a missing feature (the 类型 column and the
top tabs cover type filtering correctly).

The invariant is "every piece of text a cell can display must be matched by that
column's filter". Since the offending fallback is localized, the fix belongs on
the display side: no-value rows now show the language-neutral placeholder `—`,
which the sibling 用户 column (`t.user_name || "—"`) and the Key column's own
consume/earn branch already used. Both sides then share one definition:

  front  `t.model || "—"`                        `t.key_name || t.key_label || "—"`
  SQL    `COALESCE(NULLIF(model, ''), '—')`      `COALESCE(NULLIF(ak.name,''), NULLIF(<key_label>, ''), '—')`

The SQL needs the `NULLIF` layer-by-layer because JS `||` treats the empty
string as missing while `COALESCE` only falls through on NULL — the same
asymmetry C2101 fixed for an empty `api_keys.name`. The exported CSV needs no
change: it prints `t.model` / `t.key` from the same view row, so it follows the
cell (the same-口径 rule established for 点数 in C2054 and for 时间 in C2111).

Tests: `cargo test` 235 -> 238 (three new tests: the placeholder must be
findable through both column filters, the real-model-name path must keep
working, and a negative control asserting the localized type label is NOT a
filterable value — the guard for the rejected alternative of hardcoding Chinese
labels into SQL). Verified with a jsdom probe (real ui/index.html + four real
scripts, only `fetch` and the download stubbed) that reads the rendered cells,
the wire parameter and the exported CSV bytes.

A/B across both instruments, baseline pinned to 2be0d54 via `git show` (the
driver aborts if the baseline equals the working tree) — red sets:
  v0_orig   pre-change both sides                B1 B2 B3 B4 R_main
  m_nosql   display fixed, filters reverted      R_main
  m_nodisp  filters fixed, display kept          B1 B2 B3 B4
  m_label   display kept + zh labels in the SQL  B1 B2 B3 B4 R_main R_neg
  live      the working tree                     none (14/14)
m_nosql and m_nodisp are disjoint, so each half is pinned by its own
instrument; the built-in control A0 and all five non-axis controls are green in
every leg. Recorded, not claimed away: m_label's red set contains v0_orig's (the
competing design fails the same front-end checks plus the negative control), and
it is rejected on the argument that a localized label cannot live in SQL.
@argszero
argszero merged commit dfa7318 into main Sep 14, 2026
1 check passed
@argszero
argszero deleted the fix/tx-model-key-cell-filter-parity branch September 14, 2026 00:51
argszero added a commit that referenced this pull request Sep 27, 2026
…315)

Three sentences in ui/README.md named something a reader cannot find.
Each is corrected in place, with the change that invalidated it named
(the idiom the file already uses for its endpoint-card and login-page
sections).

1. bump() wiring. The line claimed 4 balance-change sites including
   "raise-request approval" and an "isMe" guard. The code has 3 sites
   (4 bump() calls: two for the wallet top-up, one for the operator
   crediting themselves, one for the chat deduction). The approval site
   lived in the local mock branch that mutated D.USER.balance in place;
   89963f3 (#94, zero-mock) deleted that branch and approveRaise() now
   only reloads the list, so the bump went with it. The same refactor
   inlined the self-top-up guard to `u.email === D.USER.email`.

2. The login "remember me" bullet still said a `demo-hint` element
   showed the demo account. 35291b3 (#90, v0.6.0 demo-seed removal)
   deleted that element; the class rule in ui/css/style.css is now
   orphaned and the `login.demo` pack key already sits in the
   unreachable sunset list.

3. The C2113 section cited
   `transactions_model_and_key_filters_match_the_displayed_placeholder`.
   No such test ever existed: dfa7318 (#231) added the README line and
   a test named `tx_model_and_key_filters_match_the_displayed_placeholder`
   in the same commit. The name now matches the file it cites.

The corrections describe what was removed rather than re-printing the
removed identifiers, so a grep for a stale name still lands nowhere
(this is how the C2138 correction above already reads; the login-page
section is the deliberate exception -- there the deleted selectors are
the section's own subject).

Docs only: no production code, no i18n key, no cache-bust token. The
orphaned .demo-hint rule is left alone on purpose (removing CSS is a
separate, cache-busting change).
cargo test 413/0, fmt/clippy clean.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant