Repository navigation
fix(spec): the rowColor prescription stops handing authors the one spelling the renderer drops - #18849
Conversation
…elling the renderer drops `RowColorConfigSchema.colors` advertised "hex/token" and the `view/row-color-without-colors` diagnostic checks presence only, while the sole renderer (objectui `plugin-grid`'s `useRowColor`) resolves a `bg-` literal or a lower-cased colour NAME and returns `undefined` for everything else. The diagnostic's own `fix` string handed the author `'<hex_or_token>'`: the gate fired, prescribed a hex, the hex parsed, published, turned the gate green and coloured nothing. - the `colors` describe now names the two spellings that reach a class - the `fix` string prescribes a resolvable colour name, pinned by feeding it back through `checkViewCompleteness` - new `view/row-color-unresolvable-value` warning catches the hex copy the presence rule structurally cannot see, judging SHAPE rather than transcribing the renderer's vocabulary Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…ence of a word A bare "never says hex" pin is passed by deleting the word, which leaves the reader who came to ask "can I paste the option colours in?" with no answer — the same silence that let the original sentence be written. Pin instead that every sentence naming a hex also names the consequence. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…p, and add the changeset Three of the four hex `colors` fixtures in `packages/spec` assert only that the shape parses, which a hex does; but a fixture is read as an example, and this corpus was demonstrating the one spelling `colorToClass` resolves to `undefined`. The deliberate "a hex parses" pin is kept once, on `RowColorConfigSchema`, annotated with where the enforcement actually lives. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…escribe Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
The "not breaking" paragraph rested on `partitionFindings`, a symbol this repository does not contain — `git grep` found exactly one occurrence, the changeset's own sentence. The mechanism it described is real; the name was not. The router is `@objectstack/lint`'s `splitBySeverity` (`packages/lint/src/authoring-rules.ts`), whose body is literally `errors: findings.filter((f) => f.severity === 'error')` and `advisories: findings.filter((f) => f.severity !== 'error')`, and every `os build` / `os validate` / scaffold path reaches advisories through it. This text ships to consumers as `CHANGELOG.md`, so an unresolvable symbol in it is a dead end for the upgrading reader who greps it — the same class of defect the card itself is about: a prescription naming something that is not there. Two other claims in the same paragraph are now stated so they can be checked rather than taken: - the objectql twin is field-only because it calls `checkFieldCompleteness` and never the view predicate (`registry.ts` imports exactly that one); - the blast-radius zero names the three hex `colors` literals that DO exist at the pinned `.objectui-sha` — all three objectui's own React test fixtures in `ObjectView.rowColorRelay-7218.test.tsx`, asserting a relay by `toEqual` and never traversing `checkViewCompleteness`. A zero that does not name its near misses is not checkable. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
…le id
`VIEW_ROW_COLOR_UNRESOLVABLE_VALUE` is a new public const on the `./kernel`
entry, so the two generated export snapshots were stale the moment it landed
and `check:api-surface` / `check:export-origins` were both red — the sibling
`VIEW_ROW_COLOR_WITHOUT_COLORS` sits in both files, which is what made the
absence readable as a gap rather than as a surface nobody tracks.
Regenerated from a REAL build, not a fast one: the tree carried no `.d.ts` at
all (a prior `OS_SKIP_DTS=1` build), and `gen:api-surface` reads the built
declarations, so under that tree it could not have run. Rebuilt first
(`check-dts-emitted: 34/34 declared declaration file(s) present`), then
regenerated only the two the aggregate proved stale.
The diff is two added lines and nothing else — no phantom removals, which is
the signature a stale `dist` would have produced here:
+ "VIEW_ROW_COLOR_UNRESOLVABLE_VALUE (const)",
+ "VIEW_ROW_COLOR_UNRESOLVABLE_VALUE": "src/kernel/...
`check:api-surface` reads it as `0 breaking (removed/narrowed), 1 added`,
which is the accept-set reading the changeset's `minor` and its
`Clause-②: yes` declaration already claimed.
Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin ca1b4b2d8ba5e9db833795ad74738225b87629d7 && git checkout ca1b4b2d8ba5e9db833795ad74738225b87629d7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 88aa326deb8c0599803643be885708d391ef356e 46f153569f47c97f2986f194510b067d458bed5e && git checkout -B drift-repro 88aa326deb8c0599803643be885708d391ef356e && git merge --no-ff 46f153569f47c97f2986f194510b067d458bed5e
node scripts/docs-audit/affected-docs.mjs --json 88aa326deb8c0599803643be885708d391ef356e |
Contract reviewServed-tier: ① Derived judgmentsAll readings are against head
② Semver level
③ Boundary flags
NOT MEASURED, plainly: spec Implemented-by: VERDICT: PASS Generated by Claude Code |
席位处置 —— PASS 收下;
|
| 渲染器认得而规则冤枉的 | 0 |
| 认得且放行 | 1610 |
| 认不得且指出 | 1808 |
| 认不得但放行(声明性的不完整) | 430 |
⇒ 假阳性零 —— 而假阳性才是这条规则更坏的失效方向(冤枉一个能用的值,等于把作者从对的路上赶走)。那 430 个「认不得但放行」是故意的,并且以一条 NON-rule 测试钉住(chartreuse 期望 [])。
⭐ 药方本身也被验了,而这正是本卡的主题
本卡的起因是一条门禁开出的药方会把它自己关掉。复核逐条查了新药方:两处 fix 串现在给的是字面量 red,而 red 在钉住的 sha 上解析成 bg-red-100;药方里提到的每个颜色名(red / blue / slate)在钉住的树上都解析得出。陷阱字面量 hex_or_token 在 head 上只剩两处历史注释。
red——是复核的转写读数,不是测试,而且在本仓内做不成测试。
⚖️ --strict 分级:本席裁 加法,不破坏
复核实测:os lint --strict(lint.ts:910-920)与 os validate --strict(validate.ts:643)把 warning 计入失败。本席现读源码复证:
const failing = errors.length + (strict ? warnings.length : 0);⇒ 一个带 hex rowColor.colors 的三方 stack,在本 PR 发运后跑 --strict 会红。那是真的。
但本席裁它不是收窄,依据有二:
-
接受集没有动 ——
RowColorConfigSchema.colors仍是z.record(z.string(), z.string()),一个 hex 照样解析通过。按SKILL.md:476的判据(放宽接受集或扩大公开面),本 PR 命中的是扩大公开面(一个新导出、一个新规则成员)⇒Clause-②: yes正确,而不是(narrowing)。 -
⭐
--strict这个旗标自己的 docblock 写明了它的用途(逐字):--strict(cli:os lint --strict— a flag that makes warning-severity findings fail the run, so an app can rely on the 119 warning-level rules instead of re-implementing them locally (epic hotcrm#1579, step 1) #15935) makes awarningfail it too — so an app can rely on the warning-level rules this registry ships as its gate instead of re-implementing them locally at error level⇒ 「注册表新增一条 warning 级规则会让
--strict变红」正是这个旗标被设计出来要做的事,不是它被破坏。选择--strict的 app 已经选择了「registry 新增的 warning 会成为我的闸门」。
仓内先例同向:layout-without-binding 与 tree-without-parent-field 都以 Minor 发运且无横幅。
⇒ 不挂 BREAKING 横幅、不加 ADR-0087 处置标记;minor 维持。
🔴 但那句话要改 —— 它会变成永久的 CHANGELOG
changeset 现在写「os build / os validate / os lint still exit 0」。默认路径为真,--strict 下为假。 一个在 CI 里跑 os lint --strict 的三方 app 升级后会红,而 CHANGELOG 里没有任何一句提醒它。
⇒ 补丁轮已派发,范围锁死为那一句加一个限定词 + 一句 --strict 说明。⛔ 不改代码、⛔ 不改分级、⛔ 不加横幅。
本席另立卡的两处(⛔ 不在本轮)
- objectui 侧残留(读取面只读,派发令禁止编辑):
useRowColor.ts:13-14那段说「用的是 CSS custom property」的 docblock 在钉住的 sha 上仍在且为假;三处 hex relay 夹具;以及bg-直通既不宽容大小写也不宽容空白(bg-red-100会被丢掉)—— 新规则正确地镜像了这个行为。 - 词表声明在哪 —— 本卡自己的未决问题:让两侧不能漂移的那个声明点在哪里。分诊说它要自己的载体,本 PR 故意不回答,且复核确认 diff 里没有任何东西预判它。
复核声明的 NOT MEASURED,本席照录不打折
spec 的 test / test:repo / typecheck / check:generated 本地未测 —— 锁被两个在飞 dev 占着,复核把记录逼出来时仍在排队,并且主动停了自己的驱动,不让盒子欠锁时间。本席拿到的读数是 CI 在该 head 上全新 checkout 的绿(Test Core 6/6 分片 + rollup、Type Check · workspace、TypeScript Type Check、Lint & Repo Gates 于 00:41:40Z 结束)。
Generated by Claude Code
…quence The exit-0 sentence held only on the default path. `os lint --strict` (`failing = errors + (strict ? warnings : 0)`) and `os validate --strict` (`flags.strict && warnings.length > 0 ? 1 : 0`) both promote a warning to a failure, and the new `view/row-color-unresolvable-value` finding reaches both through `runAuthoringRules` -> `splitBySeverity` -> advisories. A third-party stack carrying a hex `rowColor.colors` map and gating CI on either strict run therefore goes red on upgrade, with no word about it in the CHANGELOG this changeset compiles into. Prose only: the accept set, the rule, its severity and the declaration are unchanged. Claude-Session: https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho Co-authored-by: Claude <noreply@anthropic.com>
📌 两条记录性更正 —— 复核记录
|
Contract reviewServed-tier: ① Derived judgmentsNarrow delta review extending record
② Semver levelUnchanged from ③ Boundary flags
NOT MEASURED, plainly: no package suite (none is owed for a changeset-only diff; nothing was queued on the verify lock); Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #18791
Clause-②: yes
RowColorConfigSchema.colorsadvertisedMap of field value to color (hex/token), andthe
view/row-color-without-colorsdiagnostic checked PRESENCE only. The sole renderer —objectui
plugin-grid'suseRowColor— resolves far less than that. So the chain ran:the gate fires, the gate's own
fixstring hands the author a hex, the hex parses,publishes, clears the
!config.colorsguard, turns the gate GREEN, and colours nothing.A control whose own prescription switches it off.
What the renderer actually does
Read at the pinned
.objectui-sha53ded82bf7a494f54e344e19099dbf00854b8694, not atobjectui's local HEAD (a different tree this repo does not consume):
COLOR_TO_CLASShas 23 entries, every key a bare lower-case word (red,slate,grey, …), each mapping tobg-NAME-100.colorToClassreturns abg--prefixed value untouched; otherwise it looks upcolor.toLowerCase().trim()withhasOwnPropertyand returnsundefinedforeverything else. Tailwind v4 has no runtime, so no class can be fabricated from a hex.
Three landing points
describenow names the two spellings that reach a class and names a hex onlyas the thing that does not.
fixstring (highest priority — the only half that ACTIVELY pushed authorsinto the trap) now prescribes a resolvable colour name.
tokenwent with the hex: itnamed nothing an author could look up and stood beside hex as an equal alternative.
view/row-color-unresolvable-value— the halfpresence-only structurally cannot see, because a hex map CLEARS the guard that
silences the older rule.
The new rule judges the shape a value has and deliberately does not transcribe
objectui's 23-entry map. Two structural facts carry it, and neither depends on what the
map contains: the
bg-branch tests the raw value, and every key is a bare lower-caseword matched after
toLowerCase()andtrim(). That makes it sound — it neveraccuses a value the renderer would have resolved, including
'RED'and' red '— anddeliberately incomplete: an unknown name such as
chartreuseis shaped like a key andis passed, pinned as a NON-rule. A hand-copy of another repo's vocabulary is a second
opinion that drifts silently in both directions.
Item 3 was gated on blast radius — measured, and it clears
The gate: if the rule would refuse anything currently authored, stop and report.
warning, and@objectstack/lint'ssplitBySeveritysorts everything that is noterrorinto advisories, soos build/os validate/os lintstill exit 0 ontheir default paths. The registration-time twin in
@objectstack/objectqlcallscheckFieldCompletenessand never the view predicate, and warns without ever throwing.5723359135): underos lint --strictandos validate --stricta warning IS a failure —lint.ts:922computes
failing = errors.length + (strict ? warnings.length : 0)andvalidate.ts:715exits 1 onflags.strict && a non-zero warning count— so a stackcarrying an unresolvable
rowColor.colorsvalue starts failing those strict runs.That is what the flag is for (its own docblock: so an app can rely on the
warning-level rules this registry ships as its gate), which is why the seat ruled
this additive rather than breaking; the changeset now states the consequence and the
fix. ⛔
os buildis NOT in that pair:build.tsis an alias for Compile, whichcarries
--strict-bodyand no--strict.rowColor.colorsmap isexamples/app-showcase's task grid —{ low: 'slate', medium: 'blue', high: 'amber', urgent: 'red' }— four colour names, all resolving. Every otherrowColorin the treeis
field-only and belongs to the older rule. Grep controls run both ways: a litcontrol hitting 11 lines under
examples/, a fabricated dark control returning exit 1.colorsliterals, named here so thezero is checkable rather than asserted: all three are objectui's own React test
fixtures (
ObjectView.rowColorRelay-7218.test.tsx, inapp-shellandplugin-view).They assert a relay by
toEqualand never traversecheckViewCompleteness, so thisrule does not judge them and does not change their verdict.
Verification
Round resumed after a container restart killed the previous session mid-flight; nothing
it implied was taken on trust, and re-measuring found two real gaps, both fixed here.
pnpm --filter @objectstack/spec buildcheck-dts-emitted: 34/34pnpm --filter @objectstack/spec test(projectlocal)pnpm --filter @objectstack/spec test:repo(projectrepo)pnpm --filter @objectstack/spec typecheckpnpm --filter @objectstack/spec check:generatedcheck-adr-0087-registration/check-changeset-no-major/check-empty-changesetpnpm check:nul-bytes,check-spec-docblock-symbol-anchorsCounts read against
c5e927f9506. Heavy runs went throughscripts/pm/os-verify-lock.sh; every exit code was captured after a redirect, neverthrough a pipe.
Gap 1 — the changeset named a symbol that does not exist. Its "not breaking"
paragraph rested on
partitionFindings;git grepfound exactly one occurrence in therepository, the changeset's own sentence. The mechanism was real, the name was not — the
router is
splitBySeverity(packages/lint/src/authoring-rules.ts). Corrected, becausethis text ships to consumers as
CHANGELOG.mdand an unresolvable symbol there is a deadend for the reader who greps it — the same defect class as the card itself.
Gap 2 — two generated artifacts were stale.
VIEW_ROW_COLOR_UNRESOLVABLE_VALUEis anew public const on
./kernel, socheck:api-surfaceandcheck:export-originswereboth red. The tree carried no
.d.tsat all (a priorOS_SKIP_DTS=1build), under whichgen:api-surfacecannot run — so this was rebuilt for real first, then only the two theaggregate proved stale were regenerated. The diff is two added lines and nothing else;
check:api-surfacereads it as0 breaking (removed/narrowed), 1 added, which is theaccept-set reading the
minorbump and the clause-② widening declaration already claimed.Test-fixture triage went by the rule's consumer radius, not by the edited package: the
RowColorConfigSchemafixture that pins "a hex does parse" is deliberately KEPT (theassertion is correct — what was wrong is believing a parse means a colour), while the
corpus fixtures that were merely demonstrating a hex were respelled, because a fixture
is read as an example.
Acceptance notes
Out of scope for this PR, noted rather than fixed:
rowColor.colorstest fixtures at the pinned sha model theexact trap this card is about, as an example an AI or a human would copy. They are
correct as relay assertions, so this is a readability trap and not a broken test, and
objectui is read-only from here. Reported to the seat with dedupe words rather than
filed by me.
content/docs/references/api/protocol.mdxandcontent/docs/references/data/object.mdxrender
rowColoras an inline type and so never expand thecolorsdescribe; onlyview.mdxcarries the nested-shape tables that received the new sentence. Generatedoutput, correct as generated — noted, not filed.
⛔ Not addressed here and deliberately untouched: the
view.exportOptionsformat enumregion of
view.zod.ts, which on-hold card #8346 declares as its trigger region. Thischange lives at the
RowColorConfigSchemadescribe and does not enter it.🤖 Generated with Claude Code
https://claude.ai/code/session_01LvwGppdonww4zGLWZo5rho
Generated by Claude Code
Generated by Claude Code