Provenance: moved from objectstack-ai/objectstack#14110 by the objectstack triage seat (session session_019kDRpB7D2XzVzkaLp57T5D, R+89) under the file-at-destination rule — the fix lands in this repo's published renderer packages, so the card lives here. Original reporter: os-warren, from objectstack-ai/duly#12, measured on @objectstack/console 17.2.0 (plugin-gantt, plugin-timeline, plugin-calendar). Rebuilt rather than transferred (transfer is unavailable on this seat's channel); the source card is closed as moved.
The inversion
GanttConfigSchema.colorField is described as "Field that drives the bar color". The renderer's task mapping does this:
letre=a ? n[a] : void0;// a = colorField, n = the recordif(!re){// only when colorField is ABSENTlete=n.status??n.state??n.priority??n.severity;if(e!=null&&e!==""){lett=cnt(void0,e);t&&(re=unt(t));}}…
style: {…,backgroundColor: n.color||"#3b82f6",…}So with colorField: 'status', color becomes the raw stored value — "open" — and lands in backgroundColor: "open". That is not a colour; the browser drops the declaration and every bar falls back to the bg-primary class. All bars identical.
With the key absent, the fallback branch runs, maps the status value through the semantic-token helpers and produces a real hex per state. Bars separate by status. Declaring the documented key is strictly worse than omitting it, with no error, warning or console message either way.
The same key means three different things across the three renderers
| renderer | colorField: '…' resolves to |
|---|
plugin-timeline | the authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color |
plugin-calendar | not a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour |
plugin-gantt | the raw value, straight into backgroundColor — invalid CSS, no colour at all |
An author who colours three lenses by one field gets three unrelated results, one of which is "no colour".
Ruled route (triage)
Make gantt resolve colorField the way plugin-timeline already does — it has objectSchema in hand:
- if the field has
options and the record's value matches one with a color, use that colour; - else if the value already looks like a colour or a known palette token, use it (today's behaviour);
- else fall through to the existing semantic-token derivation instead of emitting an invalid CSS value.
Then align calendar onto the same ladder so the three agree — timeline is the reference implementation; lift its resolver somewhere all three call. Follow-up owed to the spec lane after this lands (per the multi-repo rule, filed by the seat that accepts this PR): GanttConfigSchema / CalendarConfigSchema.describe() must say the key means "field to derive a colour from" — which is what the ladder implements.
What the reporting app shipped meanwhile
The gantt view in duly declares nocolorField, with the measurement in a source comment so nobody "fixes" it back; the timeline view does declare it, because there it works.
Grade:pm:queue · priority:p2 · domain:ui · type Bug. Size/model suggestion: S–M, opus; the deliverable's proof is one fixture per renderer showing the same status value rendering the same authored colour.
Refs: objectstack-ai/objectstack#14110 (source, closed as moved) · objectstack-ai/duly#12.
Provenance: moved from objectstack-ai/objectstack#14110 by the objectstack triage seat (session
session_019kDRpB7D2XzVzkaLp57T5D, R+89) under the file-at-destination rule — the fix lands in this repo's published renderer packages, so the card lives here. Original reporter:os-warren, fromobjectstack-ai/duly#12, measured on@objectstack/console17.2.0 (plugin-gantt,plugin-timeline,plugin-calendar). Rebuilt rather than transferred (transfer is unavailable on this seat's channel); the source card is closed as moved.The inversion
GanttConfigSchema.colorFieldis described as "Field that drives the bar color". The renderer's task mapping does this:So with
colorField: 'status',colorbecomes the raw stored value —"open"— and lands inbackgroundColor: "open". That is not a colour; the browser drops the declaration and every bar falls back to thebg-primaryclass. All bars identical.With the key absent, the fallback branch runs, maps the status value through the semantic-token helpers and produces a real hex per state. Bars separate by status. Declaring the documented key is strictly worse than omitting it, with no error, warning or console message either way.
The same key means three different things across the three renderers
colorField: '…'resolves toplugin-timeline{value → option}fromobjectSchema.fields[colorField].optionsand readsoption.colorplugin-calendarplugin-ganttbackgroundColor— invalid CSS, no colour at allAn author who colours three lenses by one field gets three unrelated results, one of which is "no colour".
Ruled route (triage)
Make gantt resolve
colorFieldthe wayplugin-timelinealready does — it hasobjectSchemain hand:optionsand the record's value matches one with acolor, use that colour;Then align calendar onto the same ladder so the three agree — timeline is the reference implementation; lift its resolver somewhere all three call. Follow-up owed to the spec lane after this lands (per the multi-repo rule, filed by the seat that accepts this PR):
GanttConfigSchema/CalendarConfigSchema.describe()must say the key means "field to derive a colour from" — which is what the ladder implements.What the reporting app shipped meanwhile
The gantt view in
dulydeclares nocolorField, with the measurement in a source comment so nobody "fixes" it back; the timeline view does declare it, because there it works.Grade:
pm:queue·priority:p2·domain:ui· type Bug. Size/model suggestion: S–M,opus; the deliverable's proof is one fixture per renderer showing the samestatusvalue rendering the same authored colour.Refs: objectstack-ai/objectstack#14110 (source, closed as moved) · objectstack-ai/duly#12.