bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

Description

@os-warren

Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

What was measured

packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

  • chartType: 'scatter', xAxisKey: 'xm'
  • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
  • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

Result:

readingvalue
path.recharts-symbols4
.recharts-scatter groups2
distinct symbol transforms2264,5 and 475,110, each drawn TWICE

zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

Why

The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

dataKey={scatterYKey} // = series[0]?.dataKey || 'value'

So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

Why it matters

This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

Not obviously a one-line fix — it needs a shape decision

Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

  • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
  • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
  • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

Related

objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


PM state (added by the domain:ui execution seat, 2026-09-02)

Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

Blocked-by: #7399
Blocked-by: #7402
Unlock-action: re-check PR #7400

⚠️Neither blocker alone releases this card, and they are about different things:

blockerquestion
#7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
#7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

Metadata

Metadata

Labels

bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

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

    bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

    Description

    @os-warren

    Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

    What was measured

    packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

    • chartType: 'scatter', xAxisKey: 'xm'
    • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
    • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

    Result:

    readingvalue
    path.recharts-symbols4
    .recharts-scatter groups2
    distinct symbol transforms2264,5 and 475,110, each drawn TWICE

    zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

    Why

    The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

    dataKey={scatterYKey} // = series[0]?.dataKey || 'value'
    

    So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

    Why it matters

    This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

    Not obviously a one-line fix — it needs a shape decision

    Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

    • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
    • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
    • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

    Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

    Related

    objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


    PM state (added by the domain:ui execution seat, 2026-09-02)

    Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

    Blocked-by: #7399
    Blocked-by: #7402
    Unlock-action: re-check PR #7400

    ⚠️Neither blocker alone releases this card, and they are about different things:

    blockerquestion
    #7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
    #7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

    ⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

    ⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

    Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

    Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

    ⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

    Metadata

    Metadata

    Labels

    bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

    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

      bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

      Description

      @os-warren

      Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

      What was measured

      packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

      • chartType: 'scatter', xAxisKey: 'xm'
      • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
      • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

      Result:

      readingvalue
      path.recharts-symbols4
      .recharts-scatter groups2
      distinct symbol transforms2264,5 and 475,110, each drawn TWICE

      zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

      Why

      The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

      dataKey={scatterYKey} // = series[0]?.dataKey || 'value'
      

      So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

      Why it matters

      This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

      Not obviously a one-line fix — it needs a shape decision

      Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

      • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
      • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
      • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

      Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

      Related

      objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


      PM state (added by the domain:ui execution seat, 2026-09-02)

      Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

      Blocked-by: #7399
      Blocked-by: #7402
      Unlock-action: re-check PR #7400

      ⚠️Neither blocker alone releases this card, and they are about different things:

      blockerquestion
      #7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
      #7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

      ⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

      ⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

      Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

      Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

      ⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

      Metadata

      Metadata

      Labels

      bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

      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

        bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

        Description

        @os-warren

        Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

        What was measured

        packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

        • chartType: 'scatter', xAxisKey: 'xm'
        • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
        • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

        Result:

        readingvalue
        path.recharts-symbols4
        .recharts-scatter groups2
        distinct symbol transforms2264,5 and 475,110, each drawn TWICE

        zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

        Why

        The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

        dataKey={scatterYKey} // = series[0]?.dataKey || 'value'
        

        So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

        Why it matters

        This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

        Not obviously a one-line fix — it needs a shape decision

        Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

        • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
        • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
        • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

        Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

        Related

        objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


        PM state (added by the domain:ui execution seat, 2026-09-02)

        Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

        Blocked-by: #7399
        Blocked-by: #7402
        Unlock-action: re-check PR #7400

        ⚠️Neither blocker alone releases this card, and they are about different things:

        blockerquestion
        #7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
        #7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

        ⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

        ⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

        Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

        Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

        ⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

        Metadata

        Metadata

        Labels

        bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

        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

          bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

          Description

          @os-warren

          Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

          What was measured

          packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

          • chartType: 'scatter', xAxisKey: 'xm'
          • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
          • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

          Result:

          readingvalue
          path.recharts-symbols4
          .recharts-scatter groups2
          distinct symbol transforms2264,5 and 475,110, each drawn TWICE

          zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

          Why

          The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

          dataKey={scatterYKey} // = series[0]?.dataKey || 'value'
          

          So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

          Why it matters

          This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

          Not obviously a one-line fix — it needs a shape decision

          Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

          • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
          • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
          • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

          Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

          Related

          objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


          PM state (added by the domain:ui execution seat, 2026-09-02)

          Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

          Blocked-by: #7399
          Blocked-by: #7402
          Unlock-action: re-check PR #7400

          ⚠️Neither blocker alone releases this card, and they are about different things:

          blockerquestion
          #7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
          #7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

          ⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

          ⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

          Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

          Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

          ⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

          Metadata

          Metadata

          Labels

          bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

          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

            bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

            Description

            @os-warren

            Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

            What was measured

            packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

            • chartType: 'scatter', xAxisKey: 'xm'
            • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
            • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

            Result:

            readingvalue
            path.recharts-symbols4
            .recharts-scatter groups2
            distinct symbol transforms2264,5 and 475,110, each drawn TWICE

            zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

            Why

            The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

            dataKey={scatterYKey} // = series[0]?.dataKey || 'value'
            

            So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

            Why it matters

            This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

            Not obviously a one-line fix — it needs a shape decision

            Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

            • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
            • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
            • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

            Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

            Related

            objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


            PM state (added by the domain:ui execution seat, 2026-09-02)

            Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

            Blocked-by: #7399
            Blocked-by: #7402
            Unlock-action: re-check PR #7400

            ⚠️Neither blocker alone releases this card, and they are about different things:

            blockerquestion
            #7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
            #7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

            ⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

            ⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

            Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

            Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

            ⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

            Metadata

            Metadata

            Labels

            bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

            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

              bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

              Description

              @os-warren

              Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

              What was measured

              packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

              • chartType: 'scatter', xAxisKey: 'xm'
              • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
              • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

              Result:

              readingvalue
              path.recharts-symbols4
              .recharts-scatter groups2
              distinct symbol transforms2264,5 and 475,110, each drawn TWICE

              zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

              Why

              The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

              dataKey={scatterYKey} // = series[0]?.dataKey || 'value'
              

              So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

              Why it matters

              This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

              Not obviously a one-line fix — it needs a shape decision

              Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

              • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
              • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
              • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

              Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

              Related

              objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


              PM state (added by the domain:ui execution seat, 2026-09-02)

              Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

              Blocked-by: #7399
              Blocked-by: #7402
              Unlock-action: re-check PR #7400

              ⚠️Neither blocker alone releases this card, and they are about different things:

              blockerquestion
              #7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
              #7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

              ⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

              ⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

              Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

              Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

              ⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

              Metadata

              Metadata

              Labels

              bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

              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

                bug(plugin-charts): a multi-series scatter draws every series at the FIRST series' y values — two measures, one cloud painted twice #7194

                Description

                @os-warren

                Found while sweeping scatter for objectui#7171 (PR to follow). Not fixed there — a different mechanism, and outside that card's degenerate-data scope.

                What was measured

                packages/plugin-charts/src/AdvancedChartImpl.tsx, the scatter arm. Rendered in vitest with the real Recharts and a 480x320 container:

                • chartType: 'scatter', xAxisKey: 'xm'
                • series: [{ dataKey: 'ym' }, { dataKey: 'zm' }]
                • data: [{ xm: 10, ym: 40, zm: 5 }, { xm: 20, ym: 25, zm: 90 }]

                Result:

                readingvalue
                path.recharts-symbols4
                .recharts-scatter groups2
                distinct symbol transforms2264,5 and 475,110, each drawn TWICE

                zm carries 5 and 90. Neither value is anywhere on the plot. Both series are painted at ym's coordinates, in two different palette colours, with a legend naming two measures.

                Why

                The arm maps over series and renders one element per series, but each one is handed data={data} with no dataKey of its own — the y coordinate comes from the single axis:

                dataKey={scatterYKey} // = series[0]?.dataKey || 'value'
                

                So every series reads the same two keys off the same rows. A second series adds a colour and a legend entry and nothing else.

                Why it matters

                This is not a degenerate-data case: the data is perfectly good and the chart is confidently wrong. A reader comparing two measures sees them agree exactly, on every point, always. The legend is the only thing asserting there are two series, and it is the only part that is true.

                Not obviously a one-line fix — it needs a shape decision

                Recharts models a multi-series scatter as several elements each with their own data array, not as one array with several y columns. Candidate shapes, none ruled:

                • A. Keep one numeric YAxis; give each element data={data.map(r => ({ x: r[xKey], y: r[s.dataKey] }))} and bind the axes to the projected keys. Cheap, works, changes what onClick payloads carry.
                • B. Refuse a second series on scatter, under the file's existing refusal shell, until a real caller needs it. Consistent with the startup-scope discipline if nothing authors a two-series scatter today.
                • C. Leave it and pin the current behaviour as intended (a scatter's second "series" being only a colour band).

                Whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that.

                Related

                objectui#7171 (the sweep that found it) · objectui#7147 / PR objectui#7169 · objectui#4683.


                PM state (added by the domain:ui execution seat, 2026-09-02)

                Ruling B is implemented and delivered on PR #7400, and the Clause-② contract review at CONTRACT_REVIEW_TIER has returned PASS WITH REQUIRED AMENDMENTS. The card is parked on two independent maintainer decisions, not on outstanding implementation work.

                Blocked-by: #7399
                Blocked-by: #7402
                Unlock-action: re-check PR #7400

                ⚠️Neither blocker alone releases this card, and they are about different things:

                blockerquestion
                #7399the framework per-chunk eager-closure ceiling — 177 bytes repo-wide against the ruled copy's +420
                #7402does ruling B reach a compareTo scatter — a published capability the ruling never named?

                ⭐ The card's own open measurement — "whether any dataset in the product actually authors a two-series scatter should decide between B and A; I did not measure that" — was made during implementation and then widened by the reviewer to the dashboard-widget spelling (type: 'scatter' | 'bubble') that the implementer's regex could not see: 0 either way, with a control firing at 10 files. The zero holds on a stronger basis than either party first claimed.

                ⚠️ But #7402 is precisely the gap that census cannot cover: a compare-to scatter is one authored series plus one synthesised overlay, so it is not an authored two-series scatter and the count that justified B is blind to it.

                Byte fork, measured in two halves: the mechanism costs 0 bytes (plugin-charts is lazy and not in the eager closure); the entire +420 is the ten-pack copy. ⛔ The lane did not raise the ceiling and did not ship less than ruling B specifies.

                Contract review outcome: mechanism PASS — precedence correct and masking nothing, no new export, 13/13 pins green with reverse verification reproduced exactly. Two required amendments are dispatched (a false claim about what check:i18n-keys covers, verified false by mutation; and the PR asserting #7402's scope as though ruled). needs:contract-review stays on PR #7400 until the seat verifies both.

                ⚠️Recorded on #7402 and deliberately not fixed here: on the compare-to path the refusal names a fix the author cannot act on — it says "Keep exactly one series: amount, amount__comparison" while the author wrote exactly one series and never wrote amount__comparison. That defect holds whichever way #7402 rules; its remedy depends on the ruling.

                Metadata

                Metadata

                Labels

                bugSomething isn't workingdomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatplugin: chartspm:blockedpriority:p1

                Type

                No type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions