From a522f86267543a396557fac1167ef032874a77af Mon Sep 17 00:00:00 2001 From: Oleh K Date: Tue, 29 Sep 2026 10:59:14 +0200 Subject: [PATCH] Update supported browsers doc and pin browserslist (#80477) * GDGT-3128: Pin browserslist to the versions we actually support The documented floor (Chrome 70, Firefox 68, Edge 17, Safari 11) had drifted far from reality. Auditing the shipped bundles turned up features that break well above it: - subgrid (Chrome 117), unguarded in two CSS modules - :has() (Firefox 121), 248 occurrences; the postcss polyfill is disabled - color-mix() (Chrome 111), including the ui/colors base palette - toSorted/findLast (Safari 16.4) and URL.canParse (Chrome 120, Safari 17) There are no polyfills to fall back on: core-js is not a dependency, so preset-env injects nothing. browserslist said "defaults", which resolves to Chrome 109 among others -- below our own floor -- so the build was targeting browsers the code breaks on. Pin it to the audited versions instead. The rspack swc-loaders hardcoded env.targets: ["defaults"], bypassing package.json entirely. Dropping targets lets swc fall back to the browserslist config, matching babel-loader and postcss. Verified against the real postcss pipeline over all 826 CSS files. Prefix output only improves: -webkit-backdrop-filter is now emitted (Safari needs it through 17.6, and it was previously missing), and -webkit-background-clip is dropped, which is safe because Chrome 120 is exactly where the unprefixed form lands. The -moz-column-*/-o-* prefixes that go away were for Firefox versions far below 121. * GDGT-3128: Use the :global() function form in CSS modules Pinning browserslist dropped `op_mini all` from the targets, and Opera Mini was the only thing keeping postcss-preset-env on its expanded nesting desugaring. With it gone, preset-env emits the spec-correct `:is()` form: .editor :global .cm-panels -> :is(.editor :global) .cm-panels That pulls `:global` inside `:is()`, where its positional "everything after this is global" semantics no longer apply. css-loader then scoped the classes it was meant to leave alone: :local(.editor) .cm-panels -> :is(:local(.editor)) :local(.cm-panels) CodeMirror renders real `.cm-editor`/`.cm-panels`/`.cm-content` elements, so every rule in those blocks silently stopped applying -- 20 scoped selectors and 0 global in CodeMirror.module.css alone. That took out three e2e specs: cc-typing-suggestion, native-reproductions and transforms. The `:global()` function form binds global-ness to a selector node instead of a position, so no amount of selector rearrangement can separate it from the class. Verified to hold three levels deep, including under `:is()` wrapping. Selector output is otherwise unchanged: compiling each file under the old and new configs and comparing the post-CSS-modules selector sets gives an exact match, except for a `::-moz-placeholder` that the browserslist pin drops on its own (Firefox 121 has it unprefixed). The stylelint rule catches the bare form. It also flags a top-level `:global {}` block, which happens to work today only because a lone `:global` is a single compound and escapes the `:is()` wrap -- one more nesting level and it breaks the same way. * GDGT-3128: Raise the Safari floor to 18.2 for auxclick `auxclick` lands in Safari and iOS Safari 18.2 -- every version through 18.1 reports `n` in caniuse. There are 12 unguarded `onAuxClick` handlers in app source (19 counting tests), so 17 was never a floor we actually met. Chrome (55), Firefox (53) and Edge (79) are all far below their current floors, so only Safari moves. Side effect: `-webkit-backdrop-filter` is no longer emitted. Safari needed it only through 17.6, so past 18.2 autoprefixer correctly drops it. The same bump also covers scrollbar-gutter/width/color, which all wanted exactly Safari 18.2. --- .stylelintrc.json | 6 + .../supported-browsers.md | 10 +- .../components/AnsiLogs/AnsiLogs.module.css | 92 +++--- .../CodeMirror/CodeMirror.module.css | 286 +++++++++--------- .../MetabotAgentSuggestionMessage.module.css | 18 +- .../MetricSearchInput.module.css | 50 ++- .../CodeMirrorEditor.module.css | 66 ++-- .../expressions/Editor/Editor.module.css | 20 +- package.json | 6 +- rspack.iframe-sdk-embed.config.js | 2 +- rspack.main.config.js | 4 +- 11 files changed, 276 insertions(+), 284 deletions(-) diff --git a/.stylelintrc.json b/.stylelintrc.json index d3bfd1b4894..ec072d25fdb 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -28,6 +28,12 @@ "selector-pseudo-element-colon-notation": null, "no-empty-source": null, "font-family-no-missing-generic-family-keyword": null, + "selector-disallowed-list": [ + ["/:global$/"], + { + "message": "Use :global(.foo) on each selector instead of a bare :global, which breaks when combined with nesting." + } + ], "metabase/no-base-colors": true } } diff --git a/docs/installation-and-operation/supported-browsers.md b/docs/installation-and-operation/supported-browsers.md index daa059a0144..d33ae06f483 100644 --- a/docs/installation-and-operation/supported-browsers.md +++ b/docs/installation-and-operation/supported-browsers.md @@ -8,11 +8,11 @@ redirect_from: We try our best to make sure Metabase works in as many browsers as possible, but as this is the Internet, there may be little quirks from time to time in different settings. We believe Metabase works on these versions of these browsers and will attempt to fix specific bugs if any are found: -- Chrome (v70+) -- Firefox (v68+) -- Microsoft Edge (v17+) -- Safari (v11+) +- Chrome (v120+) +- Firefox (v121+) +- Microsoft Edge (v120+) +- Safari (v18.2+) -Metabase may run perfectly well on older versions of these browsers or on specific browsers not listed above, but your mileage may vary. In particular, we ended support for IE11 in Metabase 0.40: it mostly still works, but you may see some unexpected behavior. +Metabase may run on older versions of these browsers or on browsers not listed above, but your mileage may vary. We recommend you use the most up-to-date browser you can. diff --git a/frontend/src/metabase/common/components/AnsiLogs/AnsiLogs.module.css b/frontend/src/metabase/common/components/AnsiLogs/AnsiLogs.module.css index 1ccdc8eeef5..a3f6c4cace8 100644 --- a/frontend/src/metabase/common/components/AnsiLogs/AnsiLogs.module.css +++ b/frontend/src/metabase/common/components/AnsiLogs/AnsiLogs.module.css @@ -1,64 +1,62 @@ .ansiLogs { - :global { - .react-ansi-style-bold { - font-weight: bold; - } + :global(.react-ansi-style-bold) { + font-weight: bold; + } - .react-ansi-style-dim { - font-weight: lighter; - } + :global(.react-ansi-style-dim) { + font-weight: lighter; + } - .react-ansi-style-italic { - font-style: italic; - } + :global(.react-ansi-style-italic) { + font-style: italic; + } - .react-ansi-style-inverse { - color: var(--mb-color-text-primary); - background-color: var(--mb-color-background_page-primary); - } + :global(.react-ansi-style-inverse) { + color: var(--mb-color-text-primary); + background-color: var(--mb-color-background_page-primary); + } - .react-ansi-style-hidden { - color: transparent; - } + :global(.react-ansi-style-hidden) { + color: transparent; + } - .react-ansi-style-underline { - text-decoration: underline; - } + :global(.react-ansi-style-underline) { + text-decoration: underline; + } - .react-ansi-style-strikethrough { - text-decoration: line-through; - } + :global(.react-ansi-style-strikethrough) { + text-decoration: line-through; + } - .react-ansi-style-black { - color: var(--mb-color-text-primary); - } + :global(.react-ansi-style-black) { + color: var(--mb-color-text-primary); + } - .react-ansi-style-white { - color: var(--mb-color-text-primary-inverse); - } + :global(.react-ansi-style-white) { + color: var(--mb-color-text-primary-inverse); + } - .react-ansi-style-gray { - color: var(--mb-color-text-disabled); - } + :global(.react-ansi-style-gray) { + color: var(--mb-color-text-disabled); + } - .react-ansi-style-red { - color: var(--mb-color-core-red-saturated); - } + :global(.react-ansi-style-red) { + color: var(--mb-color-core-red-saturated); + } - .react-ansi-style-green { - color: var(--mb-color-core-green-saturated); - } + :global(.react-ansi-style-green) { + color: var(--mb-color-core-green-saturated); + } - .react-ansi-style-yellow { - color: var(--mb-color-core-yellow-saturated); - } + :global(.react-ansi-style-yellow) { + color: var(--mb-color-core-yellow-saturated); + } - .react-ansi-style-blue { - color: var(--mb-color-core-blue-saturated); - } + :global(.react-ansi-style-blue) { + color: var(--mb-color-core-blue-saturated); + } - .react-ansi-style-purple { - color: var(--mb-color-core-purple-saturated); - } + :global(.react-ansi-style-purple) { + color: var(--mb-color-core-purple-saturated); } } diff --git a/frontend/src/metabase/common/components/CodeMirror/CodeMirror.module.css b/frontend/src/metabase/common/components/CodeMirror/CodeMirror.module.css index fe4a225a2e3..5b419152275 100644 --- a/frontend/src/metabase/common/components/CodeMirror/CodeMirror.module.css +++ b/frontend/src/metabase/common/components/CodeMirror/CodeMirror.module.css @@ -3,167 +3,165 @@ } .editor { - :global { - .cm-editor { - height: 100%; - background: none; - white-space: pre; - } + :global(.cm-editor) { + height: 100%; + background: none; + white-space: pre; + } - .cm-focused { - outline: none; - } + :global(.cm-focused) { + outline: none; + } - /* CodeMirror's search panel defaults to z-index 300, which leaks out of the - editor and renders over surrounding components and modals. Drop it so the - panel only stacks above the editor's own content. (metabase#73290) */ - .cm-panels { - z-index: 2; - } + /* CodeMirror's search panel defaults to z-index 300, which leaks out of the + editor and renders over surrounding components and modals. Drop it so the + panel only stacks above the editor's own content. (metabase#73290) */ + :global(.cm-panels) { + z-index: 2; + } - .cm-content { - color: var(--mb-color-text-primary); - background: none; - padding-block: 8px; - } + :global(.cm-content) { + color: var(--mb-color-text-primary); + background: none; + padding-block: 8px; + } - .cm-line { - background: none; - } + :global(.cm-line) { + background: none; + } - .cm-gutters { - background-color: var(--mb-color-background_page-secondary); - border: none; - } + :global(.cm-gutters) { + background-color: var(--mb-color-background_page-secondary); + border: none; + } - .cm-gutterElement { - font-weight: bold; - font-size: 10px; - color: var(--mb-color-text-disabled); - vertical-align: baseline; + :global(.cm-gutterElement) { + font-weight: bold; + font-size: 10px; + color: var(--mb-color-text-disabled); + vertical-align: baseline; + display: flex; + align-items: center; + justify-content: flex-end; + + span[title="Fold line"] { + position: relative; + top: -3px; + } + } + + :global(.cm-activeLineGutter) { + background: none; + } + + :global(.cm-selectionBackground) { + background: var(--mb-color-input-focus) !important; + border-radius: 1px; + } + + :global(.cm-selectionMatch) { + background: none; + } + + :global(.cm-cursor) { + border-left-width: 1.6px; + border-left-color: var(--mb-color-text-secondary); + } + + :global(.cm-tooltip:not(.cm-tooltip-autocomplete)) { + border: none !important; + background: none !important; + } + + :global(.cm-matchingBracket) { + background: none !important; + outline: 0.5px solid var(--mb-color-text-disabled); + border-radius: 2px; + } + + :global(.cm-snippetField) { + font-style: italic; + background: none; + } + + :global(.cm-completionMatchedText) { + text-decoration: none; + font-weight: bold; + color: var(--mb-color-text-brand); + } + + :global(.cm-completionDetail) { + color: var(--mb-color-text-disabled); + font-style: normal; + text-align: right; + overflow: hidden; + text-overflow: ellipsis; + } + + :global(.cm-completionIcon) { + display: none; + } + + :global(.cm-completionLabel) { + display: block; + max-width: 18rem; + overflow: hidden; + text-overflow: ellipsis; + font-weight: bold; + } + + :global(.cm-tooltip-autocomplete) { + box-shadow: 0 5px 14px rgba(0, 0, 0, 0.08); + border-radius: 4px; + background-color: var(--mb-color-background_page-primary); + color: var(--mb-color-text-primary); + border: 1px solid var(--mb-color-border-neutral); + max-width: 520px; + + ul li { + color: inherit; + padding: 0 8px !important; display: flex; - align-items: center; - justify-content: flex-end; + justify-content: space-between; - span[title="Fold line"] { - position: relative; - top: -3px; + &:first-child { + margin-top: 4px; + } + + &:last-child { + margin-bottom: 4px; + } + + &:hover, + &[aria-selected="true"] { + background-color: var(--mb-color-background_surface-hover); + color: var(--mb-color-text-primary); } } - .cm-activeLineGutter { - background: none; - } + @supports (grid-template-columns: subgrid) { + ul:has(:global(.cm-completionLabel)) { + display: grid; + gap: 0 1rem; + grid-template-columns: 1fr; + grid-auto-rows: max-content; - .cm-selectionBackground { - background: var(--mb-color-input-focus) !important; - border-radius: 1px; - } - - .cm-selectionMatch { - background: none; - } - - .cm-cursor { - border-left-width: 1.6px; - border-left-color: var(--mb-color-text-secondary); - } - - .cm-tooltip:not(.cm-tooltip-autocomplete) { - border: none !important; - background: none !important; - } - - .cm-matchingBracket { - background: none !important; - outline: 0.5px solid var(--mb-color-text-disabled); - border-radius: 2px; - } - - .cm-snippetField { - font-style: italic; - background: none; - } - - .cm-completionMatchedText { - text-decoration: none; - font-weight: bold; - color: var(--mb-color-text-brand); - } - - .cm-completionDetail { - color: var(--mb-color-text-disabled); - font-style: normal; - text-align: right; - overflow: hidden; - text-overflow: ellipsis; - } - - .cm-completionIcon { - display: none; - } - - .cm-completionLabel { - display: block; - max-width: 18rem; - overflow: hidden; - text-overflow: ellipsis; - font-weight: bold; - } - - .cm-tooltip-autocomplete { - box-shadow: 0 5px 14px rgba(0, 0, 0, 0.08); - border-radius: 4px; - background-color: var(--mb-color-background_page-primary); - color: var(--mb-color-text-primary); - border: 1px solid var(--mb-color-border-neutral); - max-width: 520px; - - ul li { - color: inherit; - padding: 0 8px !important; - display: flex; - justify-content: space-between; - - &:first-child { - margin-top: 4px; + &:has(:global(.cm-completionDetail)) { + grid-template-columns: max-content 1fr; } - &:last-child { - margin-bottom: 4px; - } - - &:hover, - &[aria-selected="true"] { - background-color: var(--mb-color-background_surface-hover); - color: var(--mb-color-text-primary); - } - } - - @supports (grid-template-columns: subgrid) { - ul:has(.cm-completionLabel) { + li { display: grid; - gap: 0 1rem; - grid-template-columns: 1fr; - grid-auto-rows: max-content; + grid-column: 1 / span 2; + grid-template-columns: subgrid; + } - &:has(.cm-completionDetail) { - grid-template-columns: max-content 1fr; - } + :global(.cm-completionLabel) { + grid-column: 1; + } - li { - display: grid; - grid-column: 1 / span 2; - grid-template-columns: subgrid; - } - - .cm-completionLabel { - grid-column: 1; - } - - .cm-completionDetail { - grid-column: 2; - } + :global(.cm-completionDetail) { + grid-column: 2; } } } diff --git a/frontend/src/metabase/metabot/components/MetabotChat/MetabotAgentSuggestionMessage.module.css b/frontend/src/metabase/metabot/components/MetabotChat/MetabotAgentSuggestionMessage.module.css index 394406375c7..7b6c1a71056 100644 --- a/frontend/src/metabase/metabot/components/MetabotChat/MetabotAgentSuggestionMessage.module.css +++ b/frontend/src/metabase/metabot/components/MetabotChat/MetabotAgentSuggestionMessage.module.css @@ -7,14 +7,12 @@ } .suggestionEditor { - :global { - .cm-content { - padding: 0; - } + :global(.cm-content) { + padding: 0; + } - .cm-collapsedLines { - padding-left: 0; - } + :global(.cm-collapsedLines) { + padding-left: 0; } } @@ -25,10 +23,8 @@ transparent ); - :global { - .cm-gutters { - background: transparent; - } + :global(.cm-gutters) { + background: transparent; } } diff --git a/frontend/src/metabase/metrics-viewer/components/MetricSearch/MetricSearchInput/MetricSearchInput.module.css b/frontend/src/metabase/metrics-viewer/components/MetricSearch/MetricSearchInput/MetricSearchInput.module.css index afbb548e5c3..2ef91538b60 100644 --- a/frontend/src/metabase/metrics-viewer/components/MetricSearch/MetricSearchInput/MetricSearchInput.module.css +++ b/frontend/src/metabase/metrics-viewer/components/MetricSearch/MetricSearchInput/MetricSearchInput.module.css @@ -52,37 +52,35 @@ flex: 1; min-width: 7.5rem; - :global { - .cm-editor { - background: transparent; - } + :global(.cm-editor) { + background: transparent; + } - .cm-focused { - outline: none; - } + :global(.cm-focused) { + outline: none; + } - .cm-content { - padding: 0; - font-family: var(--mb-default-monospace-font-family); - font-size: 0.875rem; - color: var(--mb-color-text-primary); - } + :global(.cm-content) { + padding: 0; + font-family: var(--mb-default-monospace-font-family); + font-size: 0.875rem; + color: var(--mb-color-text-primary); + } - .cm-line { - padding: 0; - } + :global(.cm-line) { + padding: 0; + } - .cm-cursor { - border-left-width: 1.6px; - border-left-color: var(--mb-color-text-secondary); - } + :global(.cm-cursor) { + border-left-width: 1.6px; + border-left-color: var(--mb-color-text-secondary); + } - .cm-placeholder { - color: var(--mb-color-text-disabled); - } + :global(.cm-placeholder) { + color: var(--mb-color-text-disabled); + } - .cm-scroller { - overflow: hidden; - } + :global(.cm-scroller) { + overflow: hidden; } } diff --git a/frontend/src/metabase/querying/components/CodeMirrorEditor/CodeMirrorEditor.module.css b/frontend/src/metabase/querying/components/CodeMirrorEditor/CodeMirrorEditor.module.css index 25676d0ea4e..e696c25cc36 100644 --- a/frontend/src/metabase/querying/components/CodeMirrorEditor/CodeMirrorEditor.module.css +++ b/frontend/src/metabase/querying/components/CodeMirrorEditor/CodeMirrorEditor.module.css @@ -13,44 +13,42 @@ color: var(--mb-color-text-primary); background-color: var(--mb-color-background_page-secondary); - :global { - /* merge view styles */ - .cm-collapsedLines { - background: var(--mb-color-background_page-secondary) !important; + /* merge view styles */ + :global(.cm-collapsedLines) { + background: var(--mb-color-background_page-secondary) !important; + color: var(--mb-color-text-disabled); + + &:before, + &:after { + content: "-"; color: var(--mb-color-text-disabled); + } + } - &:before, - &:after { - content: "-"; - color: var(--mb-color-text-disabled); - } + :global(.cm-merge-b .cm-changedLineGutter) { + background: var(--change); + } + + :global(.cm-deletedLineGutter) { + background: var(--remove); + } + + :global(.cm-deletedChunk) { + background: var(--remove-light); + } + + :global(.cm-deletedText) { + background: var(--remove-mid); + } + + :global(.cm-merge-b) { + :global(.cm-changedText) { + background: linear-gradient(var(--change), var(--change)) bottom / 100% + 2px no-repeat; } - .cm-merge-b .cm-changedLineGutter { - background: var(--change); - } - - .cm-deletedLineGutter { - background: var(--remove); - } - - .cm-deletedChunk { - background: var(--remove-light); - } - - .cm-deletedText { - background: var(--remove-mid); - } - - .cm-merge-b { - .cm-changedText { - background: linear-gradient(var(--change), var(--change)) bottom / 100% - 2px no-repeat; - } - - .cm-changedLine { - background: var(--change-light); - } + :global(.cm-changedLine) { + background: var(--change-light); } } } diff --git a/frontend/src/metabase/querying/components/expressions/Editor/Editor.module.css b/frontend/src/metabase/querying/components/expressions/Editor/Editor.module.css index 2b01b36f559..a0866e4e39d 100644 --- a/frontend/src/metabase/querying/components/expressions/Editor/Editor.module.css +++ b/frontend/src/metabase/querying/components/expressions/Editor/Editor.module.css @@ -32,23 +32,19 @@ color: var(--mb-color-text-primary); margin-right: calc(28px + var(--mantine-spacing-sm)); - :global { - .cm-gutters { - border-top-left-radius: var(--mantine-radius-xs); - } + :global(.cm-gutters) { + border-top-left-radius: var(--mantine-radius-xs); } } .tooltips { - :global { - .cm-tooltip { - border: none !important; - background: none !important; - } + :global(.cm-tooltip) { + border: none !important; + background: none !important; + } - .cm-tooltip-autocomplete { - display: none; - } + :global(.cm-tooltip-autocomplete) { + display: none; } } diff --git a/package.json b/package.json index 1a3d7b42a29..e9bc20ee80a 100644 --- a/package.json +++ b/package.json @@ -518,7 +518,11 @@ "cloud:dev": "bun install && clojure -A:dev:drivers:drivers-dev:ee:ee-dev:cljs -P && bun run concurrently -n 'FE,BE' -c 'blue,green' 'bun run build-hot' 'clojure -M:dev:socket:dev-start:drivers:drivers-dev:ee:ee-dev'" }, "browserslist": [ - "defaults" + "chrome >= 120", + "edge >= 120", + "firefox >= 121", + "safari >= 18.2", + "ios_saf >= 18.2" ], "packageManager": "bun@1.3.14" } diff --git a/rspack.iframe-sdk-embed.config.js b/rspack.iframe-sdk-embed.config.js index 03dd4f40ad6..27e66bfc777 100644 --- a/rspack.iframe-sdk-embed.config.js +++ b/rspack.iframe-sdk-embed.config.js @@ -71,7 +71,7 @@ module.exports = { }, sourceMaps: false, minify: false, - env: { targets: ["defaults"] }, + env: {}, }, }, ], diff --git a/rspack.main.config.js b/rspack.main.config.js index ed92cb3a5ee..a2dffbf4bea 100644 --- a/rspack.main.config.js +++ b/rspack.main.config.js @@ -95,9 +95,7 @@ const SWC_LOADER = { sourceMaps: true, minify: false, // produces same bundle size, but cuts 1s locally - env: { - targets: ["defaults"], - }, + env: {}, }, };