Repository navigation
[fields] 徽标把选项显式 hex 颜色有损量化成 9 个色族——同族两档语义色(绿/深绿)渲染逐字相同无法区分,与 plugin-gantt 的 metadata-wins 口径相反 #5141
Description
Activity
os-support-ai commented
on Aug 18, 2026 CollaboratorMore actionsTriage: queued as Bug — an author's explicit hex option colors are lossily quantized to 9 color families so two same-family semantic colors render byte-identical, which both loses declared metadata and contradicts the metadata-wins line plugin-gantt established. If the implementer finds the quantization is a deliberate design system constraint with a recorded rationale, stop and flag back instead of forcing the fix. (Triage seat, session
session_019gCKd9EZfQ6MbGTnMHHJvW.)
Generated by Claude Code
Claim: PM loop round 1
Session:session_01AMHbqfPiETJHA95r6WzZWS
Branch:claude/issue-5141-badge-hex-fidelity
Worktree:objectui-issue-5141
Domain:repo:objectui(execution seat; this repo has nodomain:*lanes)
File surface:packages/fields/src/index.tsx(thehexToPaletteName/resolveColorName/getBadgeColorClasses/BADGE_COLOR_MAP/DOT_COLOR_MAPcluster, :1080-1280) plus new/changed tests inpackages/fields(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus—dispatch-gates.mjs --tier packages/fields/src/index.tsxreports no path-derived mandate, so this is my judgment: the card asks for a rendering-contract decision with two named routes, which is design judgment, not a mechanical edit.
Clause-②: no —options[].coloralready accepts any hex; this changes how an already-accepted value is rendered, not what is accepted or refused, and widens no public surface.
Serial constraints cleared:packages/fields/src/index.tsxlast moved in8396656(#5027, merged); no open PR and no in-flight claim touchespackages/fields. Disjoint from this round's other two cards (packages/app-shell,packages/plugin-map).Premise check, run now against
origin/main@072085de— the card's root-cause reading is confirmed verbatim:packages/fields/src/index.tsx:1134 function hexToPaletteName(hex: string): string | undefined packages/fields/src/index.tsx:1145 if (sat < 0.22) return 'gray'; packages/fields/src/index.tsx:1151 if (hue >= 345 || hue < 15) return 'red'; :1152 orange :1153 yellow :1154 green (hue < 170) :1155 blue :1156 indigo :1157 purple packages/fields/src/index.tsx:1166 if (color.charAt(0) === '#') return hexToPaletteName(color); packages/fields/src/index.tsx:1080 const BADGE_COLOR_MAP (one class set per family) packages/fields/src/index.tsx:1096 const DOT_COLOR_MAP (same, for appearance: 'dot')#2ecc71and#1e8449both land in the singlegreenbucket, so the reporter's byte-identical render follows directly. The comparator the card cites is also real:packages/plugin-gantt/src/ObjectGantt.tsx:736—explicit colorField value (hex or semantic name) — metadata wins.Carrying the triage stop-clause (comment-5326107092) as non-negotiable:
If the implementer finds the quantization is a deliberate design system constraint with a recorded rationale, stop and flag back instead of forcing the fix.
Look for that rationale first and report what you find either way — an absence measured and stated is worth as much as a hit. Note the shape of the risk: the quantizer exists so badges inherit theme-consistent Tailwind classes, and a hex-driven inline style leaves the design system's control. That is a real cost, not a bug; the question is whether it outranks losing declared author metadata.
PM ruling (this part is not up for re-litigation): where the author declared an explicit hex, the declaration wins — the platform must not silently discard authored metadata, and the gantt line already sets the house口径. The route to get there is NOT ruled — the card names two (inline style computed from the hex; or two tiers per family) and either is open to you, as is a third you can argue for on measurement. Pick on evidence and say why.
Explicit fork clause: if you find the quantization is load-bearing for theming/contrast in a way that makes honoring the hex actively wrong (e.g. dark-mode contrast falls apart, or a documented design-system constraint exists), stop and report
needs_decisionwith the evidence — do not force the fix, and do not quietly fall back to the other route. A fork reported is a good run.Do not lose the accessibility half: whatever route you take, a soft-pill badge needs a readable foreground against its computed background. If you derive light background / dark text from the hex, pin contrast, not just colour identity. The
appearance: 'dot'path (DOT_COLOR_MAP) has the same defect and is in surface — the card names it.
Generated by Claude Code
{ "issue": 5141, "status": "done", "branch": "claude/issue-5141-badge-hex-fidelity", "pr": "https://github.com/objectstack-ai/objectui/pull/5184", "premise_still_valid": true, "summary": "Stop-clause discharged: NO recorded rationale for the quantization exists. hexToPaletteName's own docstring says it exists so the explicit author colour is not ignored (approximation in service of metadata-wins); BADGE_COLOR_MAP's docstring records a rationale for the soft-pill LOOK only, which this change preserves; git archaeology is inconclusive because the checkout is shallow (51 commits). A documented constraint DOES exist (AGENTS.md section 2 and skills/objectui/rules/styling.md forbid inline styles and arbitrary colour values), but I read it as constraining HOW not WHETHER: it is not mechanically enforced (no ESLint rule; 254 style={{ in packages/*/src), it targets component-authored static styling, and data-driven author colour already reaches inline style in plugin-gantt bars, conditionalFormatting, and SchemaForm.tsx's Studio option swatch which paints this exact metadata key from the raw hex. Route (a) chosen but NOT its naive form: measurement rejected the fixed pale tint, which renders the reported pair byte-identical (#eef7f2 for both) because at -50 lightness hue/saturation differences round away in 8-bit. The declared lightness (the only dimension separating #2ecc71 l=0.49 from #1e8449 l=0.32) is therefore carried by the SURFACE, with the label derived against it. Colours ship as CSS custom properties consumed by static Tailwind utilities, so light/dark remain real dark: variants (verified in the built sheet) rather than a theme-blind inline background. Both appearance:'badge' and appearance:'dot' fixed. Non-hex paths (family names, semantic map, hashToColor) and getSemanticColorName/Gantt untouched. PR says 'Part of' because getBadgeColorClasses is class-only and cannot carry a runtime colour, so four call sites outside the ruled file surface still quantize.", "tests": "All at HEAD 26e78af0f, working tree clean at that sha (git status --porcelain empty). (1) pnpm exec vitest run packages/fields/ -> 'Test Files 106 passed (106) / Tests 1795 passed (1795)'. (2) new file: pnpm exec vitest run packages/fields/src/__tests__/badge-hex-fidelity-5141.test.tsx -> 'Test Files 1 passed (1) / Tests 54 passed (54)'. (3) pnpm --filter @object-ui/fields type-check -> echoed 'tsc --noEmit && tsc -p tsconfig.test.json', exit 0 (script name echoed, so not a zero-match silent pass). (4) pnpm --filter @object-ui/fields lint -> '814 problems (0 errors, 814 warnings)', pre-existing baseline; my two files add 2 no-explicit-any warnings matching the file's own test conventions and 3 react-refresh/only-export-components of the 87 the file already emits. (5) check:control-bytes -> OK, 4593 tracked files; plus grep -naP self-scan of both changed files: clean. (6) check:self-import, check:phantom-deps -> OK. (7) check-changeset-presence -> OK (1 changeset for 2 changed source files of 1 released package); check-changeset-no-major -> OK. (8) check:published-dist exceeded 300s (needs a full-farm build) and is left to CI; its specific concern was checked directly instead: packages/fields/dist has no __tests__ and no *.test.* entries, and dist/index.d.ts carries the new exports. ABLATION: replacing the lightness-tracking tint with the fixed pale tint turned the pin red with 'AssertionError: expected #eef7f2 not to be #eef7f2 / Tests 1 failed | 53 passed (54)', reproducing the original defect exactly. NO REBUILD NEEDED OR DONE for either leg: the test imports the subject by relative path ('../index'), not through the package exports, so vitest transforms source directly and no dist artifact participates. The fix was committed before the ablation, so the restore was git checkout of the branch (byte-identical, confirmed clean). Note the byte-inequality assertion still PASSED under ablation while the perceptual one failed, which is why the pin is a CIE76 deltaE threshold (5.0 vs a ~2.3 JND) with the colour maths written independently of the implementation. Contrast stress sample of 20 declarations incl. pure white/black/neon yellow: worst measured 4.52:1 light, 4.51:1 dark, both above the 4.5 AA floor; dot floor 1.9:1 derived by measuring today's -500 shades (yellow-500 weakest at 1.92:1). Tailwind emission verified in packages/fields/dist/index.css: all 8 utilities present, dark ones gated as ':where(.dark, .dark *)'.", "gates_run": [ "vitest packages/fields (106 files / 1795 tests) - pass", "vitest new regression file (54 tests) - pass", "type-check @object-ui/fields - pass", "lint @object-ui/fields - pass (0 errors)", "check:control-bytes - pass", "check:self-import - pass", "check:phantom-deps - pass", "check-changeset-presence - pass", "check-changeset-no-major - pass", "check:published-dist - NOT RUN locally (full-farm build, >300s); concern verified directly on packages/fields/dist" ], "files_changed": [ "packages/fields/src/index.tsx", "packages/fields/src/__tests__/badge-hex-fidelity-5141.test.tsx", ".changeset/badge-declared-hex-fidelity-5141.md" ], "deviations": [ "scripts/pm/dispatch-gates.mjs does not exist in objectui (it is an objectstack script), so the gate list was re-derived by hand from the 3-file diff rather than machine-derived.", "PR first line is 'Part of #5141', not 'Fixes': getBadgeColorClasses returns a class string and cannot carry a runtime colour, so four call sites outside the ruled file surface (ObjectGrid compact card badge, ObjectGrid group-header pills, ObjectKanban x2) still quantize. Filed as #5183. PM may flip to Fixes if the reported symptom alone is the bar.", "Three new exports added to @object-ui/fields (deriveHexBadgePalette, getBadgeHexAppearance, getDotHexAppearance) so the out-of-surface call sites can adopt this without duplicating the colour maths.", "check:published-dist not run locally - see gates_run." ], "open_questions": [ { "question": "NON-BLOCKING, maintainer policy: AGENTS.md section 2 and skills/objectui/rules/styling.md state a flat 'no inline styles / no hard-coded colour values' rule, but the repo already carries 254 style={{ uses and three data-driven author-colour paths (gantt bars, conditionalFormatting, Studio's option swatch), and this change adds a fourth. The written rule and the practised rule disagree.", "options": [ "A: amend the styling docs to carve out data-driven AUTHOR-DECLARED colour explicitly (inline style permitted only to publish CSS custom properties consumed by static utilities, as done here), keeping the ban on component-authored static colour", "B: leave the docs flat and treat every such case as a one-off exception argued in its own PR", "C: add an ESLint rule enforcing the flat ban, which would require reverting this fix and re-opening the ruling" ], "recommendation": "A. The rule's stated rationale is that semantic tokens support theming and dark mode automatically, and that rationale is fully satisfied here (colours ride CSS custom properties; light/dark stay real dark: variants). Leaving the doc flat while practice diverges is what made this card's stop-clause ambiguous in the first place, and B guarantees the same ambiguity for the next agent. C contradicts the PM ruling that declared metadata wins." } ], "out_of_scope_findings": [ "filed as #5183: ObjectGrid (compact card badge, group-header pills) and ObjectKanban (x2) call getBadgeColorClasses, which is class-string-only, so they still quantize the declared hex - after this PR one option colour renders two ways depending on surface" ] }
Generated by Claude Code
ACCEPT (engineering) — but HELD out of the merge queue pending a maintainer answer. PR #5184 (
repo:objectuiseat PM, sessionsession_01AMHbqfPiETJHA95r6WzZWS, reviewer of record).The stop-clause was discharged correctly
Triage's clause was: "If the implementer finds the quantization is a deliberate design system constraint with a recorded rationale, stop and flag back." That was checked against the right target and answered no — and the evidence cuts the other way:
hexToPaletteName's own docstring says it exists so the author's explicit colour is not ignored, i.e. the quantization was already an attempt at metadata-wins, not a decision to discard it.BADGE_COLOR_MAP's docstring records a rationale for the soft-pill look only, which this change preserves. No recorded rationale for the quantization exists. Correctly discharged, not waved past.What makes this better than the obvious implementation
The naive reading of route (a) — "compute a pale pill from the hex" — was rejected on measurement, not taste: a fixed pale tint renders the reported pair as
#eef7f2for both, because at −50 lightness the hue/saturation difference rounds away in 8-bit. That would have closed this card on paper with the user-visible bug fully intact. The declared lightness is the only dimension separating#2ecc71(l=0.49) from#1e8449(l=0.32) — their hues differ by 0.1° — so lightness is carried by the surface, putting the pair ΔE 8.0 apart instead of 2.3.The pin follows from that and is the part I'd single out: it asserts a CIE76 ΔE ≥ 5.0 (against a ~2.3 JND), with the colour maths written out independently of the implementation so it cannot agree with the code by construction. The ablation then proved the pin's own necessity — with the fix reverted, the byte-inequality assertion still passed while the perceptual one failed. A
!==pin would have shipped the bug. That is the difference between testing the fix and testing the property.Both concerns I made non-negotiable are honoured, and measured rather than asserted:
- Theming stayed with the design system. Colours ride CSS custom properties consumed by static Tailwind utilities, so light/dark remain real
dark:variants — verified in the built sheet (:where(.dark, .dark *)), not inferred. An inline background would have rendered identically in dark mode; this doesn't. - Contrast is pinned in both themes, across 20 declarations including pure white, pure black and neon yellow: worst measured 4.52:1 light and 4.51:1 dark against the AA 4.5 floor. The dot floor (1.9:1) was derived by measuring today's
-500shades (yellow-500 weakest at 1.92:1) rather than invented — so a declared dot can never be less visible than the palette dot it replaces.appearance: 'dot'was fixed too, as the card required.
Non-hex paths (family names, semantic map,
hashToColor) are untouched and pinned as such, andgetSemanticColorNameis unchanged so the Gantt path is unaffected.Honesty markers I want on the record:
check:published-distwas not run locally (>300s full-farm build) and is declared as not-run with its specific concern verified directly instead; and git archaeology was reported as inconclusive because the checkout is shallow rather than as a clean negative — the same 51-commit trap that produced false zeros for me earlier today.Part of #5141is the correct linkage, notFixes:getBadgeColorClassesis class-string-only and cannot carry a runtime colour, so four call sites inplugin-grid/plugin-kanbanstill quantize. Well filed as #5183 (pm:blocked), and the honest consequence is stated there — after this lands, one option colour renders two ways depending on surface, which is non-uniformly wrong where before it was uniformly wrong. That is a real cost of the split and it is named rather than hidden.Why it is held rather than enqueued
The blocking reason is a governed-surface policy conflict the dev raised and cannot resolve.
AGENTS.md§2 andskills/objectui/rules/styling.mdstate a flat "no inline styles / no hard-coded colour values" rule. The dev read it as constraining how rather than whether, with real evidence: it is not mechanically enforced (no ESLint rule; 254style={{inpackages/*/src), it targets component-authored static styling, and data-driven author colour already reaches inline style in three existing paths —plugin-ganttbars,conditionalFormatting, and Studio's own option swatch, which paints this very metadata key from the raw hex. That reasoning is defensible, and this change satisfies the rule's stated rationale (theming and dark mode still work automatically, via custom properties).But it is still a written rule on a governed surface, and one of the options on the table (enforce the flat ban) would require reverting this work. Landing first and asking after would be the wrong order. Escalated to the maintainer; I am not deciding it.
Clause ② — recorded rather than glossed, because my own declaration is implicated. I declared
Clause-②: noat claim time on the reasoning that this changes how an already-accepted value is rendered. The rendering half of that still holds. What the diff added that my prediction did not anticipate is three new public exports (deriveHexBadgePalette,getBadgeHexAppearance,getDotHexAppearance, plus two interfaces), correctly declared as aminorchangeset.My determination: the
nostands, because clause ②'s limbs and its stated purpose both target the contract face — what an author may declare and what is accepted or refused. That is unchanged:options[].coloraccepted any hex before and after; the fix makes the renderer honour what was already accepted. The new exports are sibling-package helper API, not authoring surface, and cannot poison a downstream author. Stating it explicitly so the call is auditable rather than silent: had the diff altered whatoptions[].coloraccepts, this would be a breach and would need a contract review by a seat other than mine.Noting an infrastructure gap while I am here:
needs:contract-reviewdoes not exist as a label in this repo, so the enqueue gate has no carrier in objectui even when it should fire. I have not minted it — a label with no established reader is theskip-changesettrap — but it means the clause-② gate is currently unenforceable here. Reported for the triage seat.Status: PR #5184 stays draft, not enqueued. CI convergence and landing follow the maintainer's answer on the styling rule.
Generated by Claude Code
- Theming stayed with the design system. Colours ride CSS custom properties consumed by static Tailwind utilities, so light/dark remain real
Maintainer ruling recorded — PR #5184 is UNBLOCKED.
Maintainer, 2026-08-18, answering the styling-rule conflict this card's implementation surfaced. Option A selected, verbatim as presented: 「A:修订文档,明写数据驱动豁免(推荐)」 — amend the styling docs to carve out data-driven author-declared colour explicitly (inline style permitted only to publish CSS custom properties consumed by static utilities, as done here), keeping the ban on component-authored static colour. Provenance:
repo:objectuiseat PM sessionsession_01AMHbqfPiETJHA95r6WzZWS, maintainer answering in-session.Consequences, both of which I am acting on:
- This PR's approach is sanctioned. The hold is lifted. PR fix(fields): render the author's declared option hex instead of quantizing it to nine palette families #5184 lands by the normal route once CI converges (currently 17 terminal / 4 running — three test shards and Type Check). No change to the diff is required: it already implements exactly the shape the ruling blesses — the colour rides a CSS custom property, the utilities stay static, and
dark:variants remain real. - The docs must now be brought into line, or the next agent hits the identical ambiguity and the triage stop-clause stays blunt for the whole class. Filed as styling rules: carve out data-driven author-declared colour, so the written rule matches the practised one #5185 and dispatched in this same round:
AGENTS.md§2 andskills/objectui/rules/styling.mdget the carve-out. That is a governed surface — I will review it and push it, but it goes to the maintainer for a human merge and I will not enqueue it.
The dev's own recommendation matched the ruling and its reasoning was the deciding evidence: the rule's stated rationale is that semantic tokens make theming and dark mode work automatically, and this implementation satisfies that rationale rather than evading it. Leaving the doc flat while practice diverged is what made this card's stop-clause ambiguous in the first place.
Generated by Claude Code
- This PR's approach is sanctioned. The hold is lifted. PR fix(fields): render the author's declared option hex instead of quantizing it to nine palette families #5184 lands by the normal route once CI converges (currently 17 terminal / 4 running — three test shards and Type Check). No change to the diff is required: it already implements exactly the shape the ruling blesses — the colour rides a CSS custom property, the utilities stay static, and
LANDED (partial) —
pm:dispatched→pm:blocked, assignee cleared.repo:objectuiseat PM, sessionsession_01AMHbqfPiETJHA95r6WzZWS.Blocked-by: #5183
Delivered: PR #5184, merged and on
mainasa09bc33ff. Verified by commit, not by report.SelectCellRenderernow renders an explicitly declaredoptions[].colorhex as declared — for bothappearance: 'badge'andappearance: 'dot'— with contrast pinned in both themes and the colours carried by CSS custom properties sodark:stays a real variant.This card stays open deliberately, and is blocked rather than queued. The PR was correctly linked
Part of, notFixes, so merging did not close it — and nothing on this card is directly dispatchable right now, which is whypm:queuewould be a false entry. Its remaining work is #5183, so the honest state is blocked on that card, with a machine-readableBlocked-by:line above for the unlock sweep to find.What is still not true for the reporter, stated plainly rather than declared victory:
getBadgeColorClassesreturns a class string and cannot carry a runtime colour, so four call sites in other packages still quantize —plugin-grid's compact card badge and group-header pills, and two inplugin-kanban. The consequence is that the same option colour now renders one way in a desktop grid cell and another in the group header above it or a Kanban card beside it. Before this PR the repo was uniformly wrong; it is now non-uniformly wrong, which is arguably harder for an author to reason about. That is a real cost of splitting the work and it is why this card does not close yet.@baozhoutao — the list/detail badge case from the original report is fixed on
main; if your app also renders these options in a Kanban board, a grid group header, or the compact/mobile card view, those follow in #5183.Also delivered from this card's implementation: the styling-rule conflict it surfaced went to the maintainer, who ruled (option A) that the docs should carve out data-driven author-declared colour. That is #5185, drafted as PR #5188 and now awaiting a human merge as a governed surface.
Generated by Claude Code
Closing as completed — the blocker cleared and the residual is closed.
repo:objectuiseat PM, sessionsession_01AMHbqfPiETJHA95r6WzZWS.Blocked-by: #5183is discharged: PR #5192 merged, onmainasfeb6b161d. Verified by commit, not by report.What is now true end to end, which is what this card actually asked for: an author's explicitly declared
options[].colorhex renders as declared on every badge surface — the desktop grid cell (PR #5184), the grid group-header pill, the grid compact/mobile card badge, and the Kanban card badge (PR #5192) — pinned by cross-surface tests rather than four independent per-site ones, because agreement between surfaces is the invariant that broke.The reporter's original symptom is gone in both directions:
#2ecc71and#1e8449no longer render byte-identically, and they are separated by a measured perceptual margin (ΔE ≥ 5.0 against a ~2.3 just-noticeable difference) rather than merely being unequal. Contrast is pinned in both themes, andappearance: 'dot'got the same treatment.Two things deliberately not done, recorded so they are not mistaken for oversights:
plugin-ganttis untouched — it consumesgetSemanticColorNameand needs family names, per this card's own ruling.plugin-calendarwas swept and is not a call site; it resolvesevent.coloron its own path and already renders the hex directly.
Residual is a different thing and is tracked separately. #5191 records that
getBadgeColorClassesremains exported and class-only, so a fifth badge surface could still reintroduce the split. That is a future trap, not a present inconsistency, and it is filed as a proposal with a recommended lint rule rather than as a defect — which is why it does not hold this card open.@baozhoutao — this is complete on
mainfor the list/detail badges, Kanban cards, grid group headers and the compact/mobile card view. Reopening is free if your deployment still shows two same-family colours rendering alike anywhere.Also delivered out of this card: the styling-rule conflict its implementation surfaced went to the maintainer, who ruled option A; the docs carve-out is #5185 / PR #5188, reviewed green and awaiting a human merge as a governed surface.
Generated by Claude Code
- added a commit that references this issue
on Aug 19, 2026 - added a commit that references this issue
on Aug 23, 2026
一句话:选项集里作者明确写的 hex 颜色,徽标渲染时被有损量化成 9 个固定色族,同一色族里的两个档次(比如「绿」和「深绿」)渲染出来逐字节相同,用户无法区分
Part of steedos-labs/os-project-titanwind-ehr#1504
现象
业务对象的 select 字段选项集声明了两档绿色:
列表/详情里两个状态的徽标渲染结果完全相同——class 逐字相同(
bg-green-50 text-green-700 border-green-200),computed style 三项(color / background / border)逐值相同。把深绿再压深到#145A32复测,输出仍一字不变。终端用户在列表上无法区分「进行中」和「已完成」。根因(main 分支源码定位)
packages/fields/src/index.tsx(main,getBadgeColorClasses一带):resolveColorName(color):hex 声明交给hexToPaletteName();hexToPaletteName():算 HSL,饱和度 sat < 0.22 归gray,否则按色相分桶——hue < 15 red / < 30 orange / < 60 yellow / < 170 green / < 238 blue / < 250 indigo / < 295 purple / else pink,共 9 个色族;BADGE_COLOR_MAP每族只有唯一一套 Tailwind 类,green=bg-green-50 text-green-700 border-green-200 …。#2ecc71与#1e8449色相都 ≈ 145°,双双落进green桶 → 输出相同。appearance: 'dot'走DOT_COLOR_MAP,同样每族一档,同病。也就是说:作者显式声明的精确色值在徽标链路上必然有损,信息量上限是 9 档;同族内的任何语义分档(浅绿/深绿、亮红/暗红……)都表达不出来,且 app 侧没有任何通道能下发精确色值(非
#且非族名的值被丢弃,退回按值名的语义映射)。期望(引用的契约)
spec 里
options[].color接受任意 hex,语义是「作者为该选项声明的颜色」;且同仓库plugin-gantt对colorField的处理已经是「原值直用 inline style」,源码注释明写 explicit colorField value (hex or semantic name) — metadata wins。徽标与甘特对同一类作者声明采取了相反的口径。诉求:声明了精确 hex 时按原值渲染(inline style,soft-pill 观感可以由 hex 现算浅底/深字),与 plugin-gantt 的 metadata-wins 口径对齐;或至少每色族提供深/浅两档。
复现版本
@objectstack/console@17.0.0(GA,打包产物dist/assets/ui-components-*.js中量化逻辑与 main 源码一致,已逐字核对);packages/fields/src/index.tsx现行代码同样成立(2026-08-18 拉取核对)。我们没有做的
app 侧未做任何 workaround(换色族=改业务口径,需求方不接受;conditionalFormatting 是整行 CSS 且优先级盖不过徽标类;列声明无单元格样式位)。app 侧 issue 已按「平台能力受限」挂起等本单。