Fix #205/#206: recover csstype values + token records reached through DeepPartial - #209
Merged
Conversation
… DeepPartial blend 0.0.38-beta.1's `ComponentTokenOverrides = DeepPartial<ComponentTokenType>` is a homomorphic mapped type that re-projects csstype `Property.*` values, stripping their alias + declaration-file provenance. The path-based `isCssType` then missed them, so csstype value-unions were ENUMERATED into giant `@unboxed` bodies with CSS-keyword constructors leaking into shared modules (#206), and the wrapping token records deepened the graph, were first reached past MAX_DEPTH, and truncated to all-`string` ghosts (#205). Three changes in src/extract.mjs: 1. Structural csstype detection: a union whose literals include the whole csstype `Globals` set is a CSS value -> `string`, however reached. An `isForeignArm` guard preserves a union that also carries a genuine record/dict/array/callable arm (chart `ColorString | GradientColorObject`, responsive `T | { [bp]: T }`) instead of dropping it to a bare `string` — this also closes a pre-existing SILENT drop of chart color record arms. The `(string & {})`/`(number & {})` open arms are recognised (prototype fingerprint) and still collapse. 2. Bounded heal relaxation: healGhostRecords accepts a rebuild that registers new entries when the record is provably bounded (boundedPastDepth), so blend's finite token tree heals while Highcharts' cyclic graph stays truncated. 3. Anonymous bounded-record escape: the materialize-past-MAX_DEPTH escape now covers the anonymous `{…}` records DeepPartial produces (safe via path-scoped stable naming). Result on blend 0.0.38-beta.1 (bindgen constant): csstype enumeration gone, monstrous names gone, record->string ghosts 350 -> 111, total loose lines 843 -> 392. Full benchmark equal-or-better (0 FAIL, 0 broken, all compile); 6 non-css packages byte-identical. New fixture csstype-deeppartial-widening; TYPE_MAPPING.md updated; generic-record-dedup improved (two ghosts -> real records). Baselines regenerated. The residual ~71 ghosts (vs beta.0's ~40 floor) are partially-degraded records blocked by the heal's 80% threshold, which is load-bearing for the #177 overload-ambiguity suppression — lowering it fakes an exact polytag in an ambiguous slot. Tracked separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Benchmark: ✅ PASS
|
commit: |
Review Finding A: in healGhostRecords the `bounded` check was `boundedPastDepth(type, …)` on the ghost record itself — but that record is registered under its own type.id, so the predicate hit the "registered → link" short-circuit and returned true unconditionally. The accept gate `newEntries === 0 || bounded` therefore collapsed to `fallbacks < bad`, the `newEntries === 0` requirement was silently gone, and the doc/comment claim that "Highcharts' cyclic graph stays false → rejected" was factually false. Replace it with `healFieldsBounded`: recurse `boundedPastDepth` over the record's FIELDS with the record's own id pre-seeded into the cycle guard. A field that links to an already-registered entry is zero-growth (safe); a field the rebuild would register anew gets the real structural check — a finite token subtree passes, a cyclic/too-deep graph trips the cycle/budget guard → false. Also read DeepPartial's SYNTHESIZED property symbols via getTypeOfSymbol (they have no declaration node, which the field recursion would otherwise reject). The gate is now honest and actually discriminates (returned 149 vs 111 before the synthesized-symbol handling was added, proving it rejects on structure). Net output is unchanged: all 122 goldens match and all 11 benchmark packages are byte-identical to the prior baseline — confirming the inert gate's practical effect was only ever bounded token records (no Highcharts was wrongly healed), so this is a pure correctness/contract fix. Also (review Finding B): document that lifting the anonymous-record name gate (change #3) applies to every package — bounded Highcharts option records now materialize (fidelity gain, 0 dangling), cyclic ones still truncate — rather than implying it is scoped to blend. Finding C (the `(string & {})` → `{ ...domProps }` open-arm shape, pre-existing) tracked in #210. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #205, #206. Follow-up: #208.
Root cause (one, shared by both issues)
blend
0.0.38-beta.1addedComponentTokenOverrides = DeepPartial<ComponentTokenType>— a homomorphic mapped type that re-projects csstypeProperty.*values, stripping their alias + declaration-file provenance. The path-basedisCssTypethen missed them, so:Property.*/Globalsvalue-unions enumerated instead of mapped tostringwhen reached throughDeepPartial<>— 872 giant@unboxedbodies + CSS-keyword constructors leak into shared namespace (blend 0.0.38-beta.1) #206 — csstype value-unions were ENUMERATED into giant@unboxedbodies, with CSS-keyword constructors (Aliceblue,AccentColor, …) leaking into shared modules.stringwhen a DeepPartial wrapper deepens the graph (blend 0.0.38-beta.1: +310 record→string regressions, source unchanged) #205 — the wrapping token records deepened the graph, were first reached pastMAX_DEPTH, and truncated to all-string"ghost" records.Confirmed with a live TS-compiler probe: a csstype value reached directly keeps its
csstypealias/path (→string); the same value throughDeepPartial<>comes back as an alias-less synthesized union.Changes (
src/extract.mjs)Globalsset (-moz-initial | inherit | initial | unset) is a CSS value →string, however it was reached. AnisForeignArmguard preserves a union that also carries a genuine record / dict / array / callable arm (chartColorString | GradientColorObject | PatternObject, responsiveT | { [breakpoint]: T }) instead of collapsing and dropping it — this also closes a pre-existing silent data-loss of chart color record arms. The(string & {})/(number & {})autocomplete open arms are recognised (prototype fingerprint) and still collapse.healGhostRecordsaccepts a rebuild that registers new entries when the record is provably bounded (boundedPastDepth), so blend's finite token tree heals while Highcharts' cyclic graph stays truncated.MAX_DEPTHescape now covers the anonymous{…}recordsDeepPartialproduces (safe via path-scoped stable naming, Structural type names churn across versions when shape is unchanged (numbered suffixes shift on unrelated upstream edits) #90/Fidelity defects in generated bindings (key-stripping, flattened unions, domProps spread, fn-as-component) + unstable anonymous-record naming — found validating #62 on blend 0.0.37-beta.6 #63).Result (blend
0.0.38-beta.1, bindgen constant)stringghosts⚪ looselinesVerification
npm testgreen (122 goldens + public-name-stability + representation-flip + module-move + html-attrs).csstype-deeppartial-widening(csstype leaf, DeepPartial leaf, and the record/dict/array over-match cases),docs/TYPE_MAPPING.mdupdated, goldens regenerated (generic-record-dedupimproved: twostringghosts → real records).Known residual (tracked in #208)
~71 of the 350 ghosts remain (beta.0's floor is ~40). They are partially-degraded records blocked by the heal's 80% threshold, which is load-bearing for the #177 overload-ambiguity suppression — lowering it fakes an exact polytag in an ambiguous overload slot (a flag-don't-fake violation). Left as a focused follow-up rather than regressing #177.
🤖 Generated with Claude Code