[finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

Description

@baozhoutao

Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

What was measured

Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

fileshape
validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

Why this is worth a card rather than a shrug

This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

Shape of the fix

object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

Metadata

Metadata

Assignees

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

    [finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

    Description

    @baozhoutao

    Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

    What was measured

    Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

    fileshape
    validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
    validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
    validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
    object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

    The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

    Why this is worth a card rather than a shrug

    This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

    Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

    Shape of the fix

    object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

    No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

    Metadata

    Metadata

    Assignees

    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

      [finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

      Description

      @baozhoutao

      Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

      What was measured

      Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

      fileshape
      validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
      validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
      validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
      object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

      The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

      Why this is worth a card rather than a shrug

      This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

      Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

      Shape of the fix

      object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

      No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

      Metadata

      Metadata

      Assignees

      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

        [finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

        Description

        @baozhoutao

        Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

        What was measured

        Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

        fileshape
        validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
        validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
        validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
        object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

        The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

        Why this is worth a card rather than a shrug

        This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

        Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

        Shape of the fix

        object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

        No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

        Metadata

        Metadata

        Assignees

        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

          [finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

          Description

          @baozhoutao

          Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

          What was measured

          Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

          fileshape
          validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
          validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
          validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
          object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

          The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

          Why this is worth a card rather than a shrug

          This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

          Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

          Shape of the fix

          object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

          No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

          Metadata

          Metadata

          Assignees

          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

            [finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

            Description

            @baozhoutao

            Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

            What was measured

            Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

            fileshape
            validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
            validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
            validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
            object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

            The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

            Why this is worth a card rather than a shrug

            This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

            Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

            Shape of the fix

            object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

            No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

            Metadata

            Metadata

            Assignees

            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

              [finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

              Description

              @baozhoutao

              Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

              What was measured

              Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

              fileshape
              validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
              validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
              validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
              object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

              The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

              Why this is worth a card rather than a shrug

              This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

              Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

              Shape of the fix

              object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

              No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

              Metadata

              Metadata

              Assignees

              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

                [finding] suggest / distance ("did you mean?") is hand-copied four times across packages/lint — the same shape #4330 already fixed for SYSTEM_FIELDS #14268

                Description

                @baozhoutao

                Recorded while implementing #14105 (PR #14267). Not fixed there: consolidating it touches rules that card does not otherwise change, so it is filed rather than ridden along.

                What was measured

                Four independent copies of the same Levenshtein "did you mean?" helper live in packages/lint/src, none of them exported, each re-deriving the same edit-distance budget:

                fileshape
                validate-object-references.tssuggest(target, known) + distance(a, b), budget max(2, floor(len/3))
                validate-sortable-fields.tssuggest(target, known) + distance(a, b), byte-for-byte the same budget
                validate-widget-bindings.tsdidYouMean(input, candidates) + levenshtein(a, b) + suggest(...), with an extra containment pre-pass for the ADR-0021 amount to sum_amount drift
                object-graph.ts (added by #14267)nearestName(target, known) + suggestName(...) + distance(a, b), the same budget again

                The fourth copy is mine, and I wrote it as a copy deliberately rather than importing one of the three: none of them exports its helper, and widening one rule's private function into a cross-rule dependency was outside #14105's declared file surface. The copy is annotated at its definition in object-graph.ts pointing here.

                Why this is worth a card rather than a shrug

                This is exactly the shape #4330 recorded and fixed one constant over — "five hand-copied SYSTEM_FIELDS lists in packages/lint should derive from the spec's declarations" — and the argument carries: with N copies, the next author improves one of N and the survivors keep the old behaviour. That is not hypothetical here, because the copies have already diverged: validate-widget-bindings.ts scores containment ahead of edit distance, so it suggests sum_amount for amount (11 edits apart, obvious to a human) where the other three suggest nothing at all. Whether that pre-pass is right is a real question — but it is currently a per-rule accident rather than a decision, and an author gets a different quality of suggestion depending on which position they typo'd.

                Two more copies exist and were checked rather than assumed to be the same question: @objectstack/formula's unknown-function suggestion and the spec's strict-object alias map are keyed on curated tables, not edit distance, so they are deliberately out of this finding's scope.

                Shape of the fix

                object-graph.ts already exports nearestName / suggestName on the package barrel, so the mechanical route is to point the other three at it and delete their private copies — plus one decision the consolidation forces into the open: whether the containment pre-pass becomes everyone's behaviour or stays declared as a widget-binding-specific one. Each rule's existing suggestion assertions are the regression net; validate-widget-bindings.test.ts in particular pins the sum_amount suggestion, so a naive merge that drops the pre-pass fails loudly rather than silently degrading messages.

                No behaviour is wrong today; this is drift-shaped, so it is filed as an observation and left unassigned for triage rather than graded here.

                Metadata

                Metadata

                Assignees

                Type

                Projects

                No projects

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions