Skip to content

objectui: four more source files carry raw control bytes — three are invisible to grep, one is a stray U+0001 #5450

Closed
objectstack-ai/objectui
#3389

Description

@yinlianghui

Found while implementing objectstack#5425 (the repo-wide scan it asks for immediately reported these). Filed separately per Prime Directive #10 — objectstack#5425's dispatch scope is useDatasetFields.ts plus the gate, so these are not fixed there.

Blocked-by: objectstack#5425 — its PR adds the KNOWN_OFFENDERS baseline map in scripts/check-control-bytes.mjs that lists these four. Fixing a file here means deleting its baseline entry in the same PR; the script fails on a stale entry, so the two cannot drift apart.

The four files

⚠️ Written as escape descriptions only, never as the byte itself — see the Notes in objectstack#5425. In this issue "the six-character escape" means backslash, lowercase u, four zeroes.

file line byte used as
packages/core/src/evaluator/listConditional.ts 71 U+0000 composite cache key, (label ?? '') + SEP + source
packages/core/src/utils/record-title.ts 45 U+0000 EMPTY_TOKEN sentinel
packages/fields/src/widgets/PeoplePicker.tsx 320 U+0000 .join(SEP) over person ids
packages/plugin-dashboard/src/DatasetWidget.tsx 89 U+0001 .join(SEP) over row-dimension values

Reproduce (the scan the gate runs):

git ls-files -z | tr '\0' '\n' | while IFS= read -r f; do
  LC_ALL=C grep -qaP '[\x00-\x08\x0b\x0c\x0e-\x1f]' "$f" 2>/dev/null && echo "$f"
done

Two different harms, and the measurement that separates them

The first three carry U+0000 and are invisible to content search today:

grep -n "const" packages/core/src/utils/record-title.ts
# => grep: packages/core/src/utils/record-title.ts: binary file matches

packages/plugin-dashboard/src/DatasetWidget.tsx is different, and worth writing down because objectstack#5425's body predicts otherwise. Measured on GNU grep 3.11 and ripgrep 14, writing one control byte per fixture file and searching for a literal on another line:

  • U+0000 is the only byte that triggers binary classification. grep prints binary file matches; ripgrep prints binary file matches (found NUL byte around offset N).
  • U+0001, U+0002, U+0007, U+0008, U+000B, U+000C, U+000E, U+001A, U+001B, U+001F and U+007F all print the matching line normally in both tools.

So DatasetWidget.tsx is not grep-blind — grep -n "rowDims" on it returns line 67 as normal text. Its defect is the other one: an invisible byte sitting in a string literal, which no reviewer can see, no diff renders, and which is indistinguishable from a typo'd neighbour. That is still a real defect (a separator nobody can read or verify), but it is not the code-search outage the other three are, and the fix priority should reflect that.

This also sharpens objectstack#5157's lesson rather than contradicting it: a NUL-only scanner does miss these, and they should not be in the tree — but the reason to reject them is "never intentional, never reviewable", not "grep goes blind". The gate landing in objectstack#5425 covers both classes and says which harm applies to which byte.

Suggested fix

Same shape as objectstack#5425's: replace each separator with a readable spelling that cannot occur in the joined values — a newline, or the six-character escape named above if the runtime value must stay byte-identical — then delete that file's entry from KNOWN_OFFENDERS. Four one-line changes; record-title.ts's EMPTY_TOKEN is a sentinel rather than a separator, so check that nothing compares it against persisted data before changing its value.

⚠️ Writing discipline: any PR or comment text describes the escape and never pastes the byte. Two of the five prior incidents in this family happened while an agent was writing the rule itself (objectstack#4763 / #4890 / #5140 / #5157). This issue's own first revision lost an escape to that exact trap: a backslash-u escape typed into a JSON tool payload was decoded into a real byte before it ever reached GitHub, then stripped — leaving empty backticks. Spell it in words.

Activity

  1. self-assigned this
    on Aug 5, 2026
  2. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    ContributorAuthor

    认领:objectui 分片 PM 滚动补位第 9 单(Blocked-by objectstack#5425/PR objectui#3388 已合并,基线已在树上,即刻解锁;摘 pm:blocked)
    会话:session_01GTRjn8xBqp75dk7kFupVRt
    分支:claude/issue-5450-control-bytes-cleanup(代码落地 objectui 仓)
    Worktree:objectui-issue-5450
    域:objectui 分片(core / fields / plugin-dashboard)
    文件面:packages/core/src/evaluator/listConditional.ts、packages/core/src/utils/record-title.ts、packages/fields/src/widgets/PeoplePicker.tsx、packages/plugin-dashboard/src/DatasetWidget.tsx + scripts/check-control-bytes.mjs(仅删对应 KNOWN_OFFENDERS 条目)+ 相应测试(越界即停)。当前在飞 0,无相交。

    口径照正文:各处换可读拼写或按 #3388 先例改 JSON 编码;record-title.ts 的 EMPTY_TOKEN 是哨兵非分隔符,改值前先核无持久化数据比较;四条基线条目同 PR 删除(门禁对陈旧条目变红,不能漂移)。⚠️ 写作纪律:PR/评论只写转义描述,不贴字节本体(本单第一版正文自己就踩过 JSON 载荷解码陷阱)。


    Generated by Claude Code

  3. yinlianghui commented on Aug 5, 2026

    @yinlianghui
    ContributorAuthor

    PM 验收:ACCEPT → objectui PR #3389 已转 ready + auto-merge(CI 15/15 settled,13 绿 2 预期跳过),跟到 MERGED。

    落地内容:四处逐一按各自语义修(listConditional 改 JSON.stringify 消灭分隔符需求;其余三处保持运行时字节同值的转义拼写,各附理由 —— EMPTY_TOKEN 经核实模块私有、从不与持久化数据比较);KNOWN_OFFENDERS 基线清空,四条同 commit 删除;反向验证含「留一条不删必须报 STALE 而非 offender」全四例复现。诚实框架记账:三处等价改写在行为级不可能产生红(转义与字节是同一运行时字符),judgement 正确 —— 行为红只该由分隔符值变化触发,新 pivot 用例恰是全套件第一个真正行使双行维分隔符的用例。无 changeset,符合 .changeset/README 的 DON'T。

    衍生:#5473(pivot cell key 用空格拼接 rowId/colId,含空格的维度值可碰撞并静默覆盖 cell 索引与 drill 目标 —— 与本单相邻行的发现)已复核入队 bug + pm:queue。

    至此 #5425 → #5450 控制字节战役全链闭环:仓内零裸控制字节、门禁常驻、基线归零。

    objectui 分片 PM session_01GTRjn8xBqp75dk7kFupVRt


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions