plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

Description

@huangyiirene

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

renderercolorField: '…' resolves to
plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
plugin-ganttthe 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:

  1. if the field has options and the record's value matches one with a color, use that colour;
  2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
  3. 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.

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
     blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
    }
    } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
    })();
    (function(){
    try {
    var __m = "github.com";
    var __re = new RegExp('^' + "github\\.com" + '
    
    Skip to content

    plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

    Description

    @huangyiirene

    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

    renderercolorField: '…' resolves to
    plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
    plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
    plugin-ganttthe 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:

    1. if the field has options and the record's value matches one with a color, use that colour;
    2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
    3. 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.

    Metadata

    Metadata

    Assignees

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

      Description

      @huangyiirene

      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

      renderercolorField: '…' resolves to
      plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
      plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
      plugin-ganttthe 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:

      1. if the field has options and the record's value matches one with a color, use that colour;
      2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
      3. 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.

      Metadata

      Metadata

      Assignees

      Labels

      bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

      Type

      No type

      Projects

      No projects

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

        Description

        @huangyiirene

        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

        renderercolorField: '…' resolves to
        plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
        plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
        plugin-ganttthe 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:

        1. if the field has options and the record's value matches one with a color, use that colour;
        2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
        3. 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.

        Metadata

        Metadata

        Assignees

        Labels

        bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

        Type

        No type

        Projects

        No projects

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
          Skip to content

          plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

          Description

          @huangyiirene

          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

          renderercolorField: '…' resolves to
          plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
          plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
          plugin-ganttthe 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:

          1. if the field has options and the record's value matches one with a color, use that colour;
          2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
          3. 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.

          Metadata

          Metadata

          Assignees

          Labels

          bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

          Type

          No type

          Projects

          No projects

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
            Skip to content

            plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

            Description

            @huangyiirene

            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

            renderercolorField: '…' resolves to
            plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
            plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
            plugin-ganttthe 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:

            1. if the field has options and the record's value matches one with a color, use that colour;
            2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
            3. 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.

            Metadata

            Metadata

            Assignees

            Labels

            bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

            Type

            No type

            Projects

            No projects

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
              Skip to content

              plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

              Description

              @huangyiirene

              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

              renderercolorField: '…' resolves to
              plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
              plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
              plugin-ganttthe 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:

              1. if the field has options and the record's value matches one with a color, use that colour;
              2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
              3. 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.

              Metadata

              Metadata

              Assignees

              Labels

              bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

              Type

              No type

              Projects

              No projects

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
                Skip to content

                plugin-gantt: colorField is passed raw into backgroundColor, so pointing it at a select field un-colours every bar — while OMITTING the key colours them correctly (moved from objectstack-ai/objectstack#14110) #7243

                Description

                @huangyiirene

                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

                renderercolorField: '…' resolves to
                plugin-timelinethe authored option colour — builds {value → option} from objectSchema.fields[colorField].options and reads option.color
                plugin-calendarnot a hex, so a stable hash of the string into a fixed palette — consistent per value, unrelated to the authored colour
                plugin-ganttthe 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:

                1. if the field has options and the record's value matches one with a color, use that colour;
                2. else if the value already looks like a colour or a known palette token, use it (today's behaviour);
                3. 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.

                Metadata

                Metadata

                Assignees

                Labels

                bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatpriority:p2

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions