Uh oh!
There was an error while loading. Please reload this page.
fix(gantt,calendar,timeline): resolve colorField through one shared ladder - #7338
Merged
Merged
Conversation
… ladder `gantt.colorField` is documented as the field that drives the bar colour, and the renderer passed the stored value straight into `backgroundColor`. Pointing the key at a select field emitted `backgroundColor: "open"` — not a colour, so the browser dropped the declaration and every bar rendered identically. Omitting the documented key was strictly better than declaring it, silently. The same key meant three different things across the three lenses: the timeline resolved the field's authored option colour, the calendar hashed the raw value onto a fixed palette, and the gantt emitted the raw value. `@object-ui/core` gains `createFieldColorResolver` — the timeline's resolver, lifted so all three call it: the field's own option colour for the record's value, else the value itself when it already is a colour literal. The last rung stays with each caller, because each has a different right answer for a value that is neither: the gantt derives a semantic-token hex, the calendar keeps its theme-aware 8-stop hash, the timeline draws its default marker. `gantt.borderColorField` takes the option-colour rung too, and keeps today's behaviour otherwise — no derivation rung, since the alert stroke is opt-in. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
Contributor
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
os-litant
marked this pull request as ready for review
September 2, 2026 08:37
Uh oh!
There was an error while loading. Please reload this page.
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 freeto 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#7243
gantt.colorFieldis documented as "field that drives the bar color", and the renderer passed the stored value straight into the bar'sbackgroundColor. Pointing the documented key at a select field emittedbackgroundColor: "open"— not a colour, so the browser dropped the declaration and every bar rendered identically. Omitting the key was strictly better, because the absent-key branch derived a real colour per status. And the same key meant three different things across the three lenses: the timeline resolved the field's authored option colour, the calendar hashed the raw value onto a fixed palette, the gantt emitted the raw value.The ladder
@object-ui/coregainscreateFieldColorResolver(packages/core/src/utils/record-color.ts) —plugin-timeline's resolver, lifted, not rewritten, so all three call one thing:colorfor the record's value;#rgb,#rrggbb,#rrggbbaa,rgb(...),hsl(...));Keyed by the option
valueonly. Deliberately notbuildOptionColorMap(chart-series.ts), which also keys by display label: that one reads dataset ROWS, where the server may have resolved a dimension to its label. These callers read RECORDS, where the stored value is the contract — and label-keying would be a tolerance the timeline never had.Falsified PM assumptions, and the two judgement calls
@object-ui/coreconfirmed by reading all three manifests, not assumed:core,components,i18n,react,typesare the packages all three already depend on. No new workspace dependency edge —check:phantom-depsgreen.@object-ui/fieldswas rejected as the home:plugin-timelinedoes not depend on it, andfieldsdepends oncore, so hosting the ladder there would either add an edge or make a cycle. That is also why rung 3 stays at the callers — it needsfields' semantic-token helpers, whichcoremay not reach.borderColorField— bounded in-place fix, named here as it must be. The stroke had the same defect from the same line shape (getSemanticHex(raw, raw)passes any unrecognised string through, so a select value reachedborderColorraw). It takes rung 1 only: an authored option colour now reaches the stroke, and everything else keeps today's behaviour byte for byte. It deliberately gains no rung 3 — the alert stroke is opt-in, and deriving one for every record would draw an alert on records that have none. Pinned by the last case in the gantt fixture (pre-fix reading'open', post-fix'#7c3aed').colorFieldpointing at a field holdingredused to paint CSSred; it now paints#ef4444. That is the behaviour the site's own contract has always promised ("hex or semantic name") and the oneborderColorFieldalready had one line below. Pinned.plugin-calendar's took 3, 6 or 8. Two in-repo spellings of one question, and the narrow one is the only one under which a valid CSS colour (#rrggbbaa) could fall past rung 2 and be replaced by a derived colour in the gantt. Widening can only turn "not recognised" into the author's colour, never the reverse. Pinned separately from the control block in the timeline fixture, because it is the one case there that was red before the lift.colorFieldpointing at a plain categorical field still reachesCalendarView.resolveEventColorwith the raw value and still hashes, unchanged. Retiring it would repaint every existing authored calendar of that shape and would trade a theme-aware class pair for a solid inline fill; that is a behaviour change beyond this card, so it was not made.CalendarView.test.tsx's five hash pins are untouched and green.calendar-view-renderer.tsx's plaincalendarblock resolvesrecord[colorField]with no object schema in hand, so there are no options to resolve there — the raw pass-through is correct on that surface and is left alone.Proof — one fixture per renderer, same
statusvalue, same authored colourThree fixtures share one shape: field
statuswith optionsopen->#7c3aed,done->#059669; recordstatus: 'open';colorField: 'status'.Pre-fix, on the unmodified tree:
ObjectGantt.colorFieldLadder-7243.test.tsx— RED,Tests 4 failed | 2 passed (6), rung 1 reportingexpected [ 'open' ] to deeply equal [ '#7c3aed' ]— the card's inversion, measured.ObjectCalendar.colorFieldLadder-7243.test.tsx— RED,Tests 1 failed | 8 passed, same[ 'open' ]vs[ '#7c3aed' ].ObjectTimeline.colorFieldLadder-7243.test.tsx— GREEN 6/6 before the lift (the control), green after.Post-fix the four suites (three fixtures plus
packages/core/src/utils/__tests__/record-color.test.ts) reportTest Files 4 passed (4)/Tests 30 passed (30).Ablation — rung 1 removed from the shared resolver at
399891d1a, mutation proven on disk before measuring (anchor count 1 -> 0, injected marker 1, blob0af3a0a0->a51561b9), restore viagit checkout HEAD -- ABSOLUTE_PATHproven by blob hash back to0af3a0a0, marker count 0,git diff HEADempty. Result:Tests 4 failed | 12 passed (16)— gantt 2 red (bar and stroke rung 1), calendar 1 red, and timeline 1 red. That last one is not the predicted direction and is reported as observed: it is the proof that the timeline is genuinely on the shared rung rather than keeping a private copy. Its other six cases stayed green, so exactly rung 1 was ablated. No rebuild was needed and none would have helped: the root vitest config aliases@object-ui/coretopackages/core/src, and the ablation going red on a tree whosediststill holds the fixed build is itself the evidence that the mutated source is what ran.Verification
Union re-run after the final commit, at
399891d1a:pnpm exec vitest run packages/plugin-gantt --maxWorkers=2->Test Files 58 passed (58)/Tests 457 passed (457)pnpm exec vitest run packages/plugin-calendar packages/plugin-timeline packages/core --maxWorkers=2->Test Files 146 passed (146)/Tests 2505 passed (2505)pnpm --filter @object-ui/core --filter @object-ui/plugin-gantt --filter @object-ui/plugin-calendar --filter @object-ui/plugin-timeline run type-check-> all fourDone. Each package'stype-checkistsc --noEmit && tsc -p tsconfig.test.json, and--listFilesconfirms all four new test files are program inputs (2 hits per package), so the tests are type-checked, not merely excluded-and-clean.check:phantom-deps,check:self-import,check:control-bytes,check:vi-mock-specifiers,check:vi-mock-inherit,check:esm-specifiers,check:side-effects-array,check:changeset-presence(9 source file(s) of 4 released package(s) changed, and this change declares 1 changeset(s)),check:changeset-no-major.pnpm exec eslint . --no-inline-config --format jsonvisited 4141 files; 0 errors in the 9 files this PR changes. The 91 errors it reports are pre-existing and in untouched files, and--no-inline-configis stricter than the per-packageeslint .CI runs.check:readme-exportsandcheck:eager-closureboth need a full-repo build (300of its303complaints are literally "type entry not on disk — runpnpm buildfirst", and the three others are ratchet floors that fall for the same reason; after building this PR's four packages, zero complaints name any of them). These are not red readings about this diff.One changeset,
patchfor all four released packages.🤖 Generated with Claude Code
https://claude.ai/code/session_01NRRumy89BYdW9ogbcdHTho
Generated by Claude Code
Generated by Claude Code