From ed61a3a6d51368588d851dac807333302fdf01fd Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Thu, 31 Oct 2019 15:48:16 -0700 Subject: [PATCH 01/10] add variable deprecation + tests --- deprecations.js | 45 ++++++++++++++++++++++++-- script/test-deprecations.js | 63 ++++++++++++++++++++++++++++++++++--- 2 files changed, 102 insertions(+), 6 deletions(-) diff --git a/deprecations.js b/deprecations.js index 3c7bd72d..d8841a08 100644 --- a/deprecations.js +++ b/deprecations.js @@ -4,6 +4,32 @@ * array and a "message" string. */ const versionDeprecations = { + '14.0.0': [ + { + variables: ['$status-pending'], + message: `This variable is deprecated.` + }, + { + variables: ['$repo-private-text', '$repo-private-bg', '$repo-private-icon'], + message: `These variables are deprecated.` + }, + { + variables: ['$marketingSpacers', '$allSpacers'], + message: `Please use the $marketing-spacers and $marketing-all-spacers variables.` + }, + { + variables: ['$exploregrid-item-border-radius'], + message: `This variable is deprecated. Use "4px" instead.` + }, + { + variables: ['$stats-switcher-py', '$stats-viewport-height'], + message: `These variables are deprecated.` + }, + { + variables: ['$min_tab_size', '$max_tab_size'], + message: `These variables are deprecated.` + } + ], '13.0.0': [ { selectors: [ @@ -68,11 +94,15 @@ const semver = require('semver') // map selectors to the version and message of their deprecation const selectorDeprecations = new Map() +const variableDeprecations = new Map() for (const [version, deps] of Object.entries(versionDeprecations)) { - for (const {selectors, message} of deps) { + for (const {selectors = [], variables = [], message} of deps) { for (const selector of selectors) { selectorDeprecations.set(selector, {version, message}) } + for (const variable of variables) { + variableDeprecations.set(variable, {version, message}) + } } } @@ -81,4 +111,15 @@ function isSelectorDeprecated(selector, version = CURRENT_VERSION) { return deprecation ? semver.gte(deprecation.version, version) : false } -module.exports = {versionDeprecations, selectorDeprecations, isSelectorDeprecated} +function isVariableDeprecated(variable, version = CURRENT_VERSION) { + const deprecation = variableDeprecations.get(variable) + return deprecation ? semver.gte(deprecation.version, version) : false +} + +module.exports = { + versionDeprecations, + selectorDeprecations, + variableDeprecations, + isSelectorDeprecated, + isVariableDeprecated +} diff --git a/script/test-deprecations.js b/script/test-deprecations.js index bc86fa06..8b987148 100755 --- a/script/test-deprecations.js +++ b/script/test-deprecations.js @@ -30,9 +30,16 @@ If either check fails, the process exits with an error status (1). process.exit(0) } -checkDeprecations(args) +Promise.all([checkSelectorDeprecations(args), checkVariableDeprecations(args)]).then( + (deprecationErrors, variableErrors) => { + const errors = deprecationErrors.concat(variableErrors) + if (errors.length) { + process.exit(1) + } + } +) -async function checkDeprecations(options = {}) { +async function checkSelectorDeprecations(options = {}) { const {bundle = 'primer', version = 'latest'} = options const currentVersion = require('../package.json').version @@ -79,9 +86,57 @@ async function checkDeprecations(options = {}) { } } - if (errors.length) { - process.exitCode = 1 + return errors +} + +async function checkVariableDeprecations(options = {}) { + const {version = 'latest'} = options + + const currentVersion = require('../package.json').version + const varsPath = `dist/variables.json` + + const local = require(`../${varsPath}`) + const remote = await fetch(`https://unpkg.com/@primer/css@${version}/${varsPath}`).then(res => res.json()) + + const {changed, added, removed} = diffLists(Object.keys(remote), Object.keys(local)) + if (changed === 0) { + console.log(`no variables added or removed in version ${currentVersion}`) + return } + + const deprecations = versionDeprecations[currentVersion] || [] + const deprecatedVariables = deprecations.reduce((list, deprecation) => list.concat(deprecation.variables), []) + console.log(`${I} ${removed.length} variables removed locally (compared with ${version})`) + console.log(`${I} ${deprecatedVariables.length} variables deprecated in v${currentVersion}`) + if (added.length) { + console.log(`${I} ${added.length} variables added`) + } + + const errors = [] + for (const deprecation of deprecations) { + for (const variable of deprecation.variables) { + if (!removed.includes(variable)) { + const error = `variable "${variable}" deprecated, but not removed` + errors.push(error) + console.log(`${X} ${error}`) + } else { + console.log(`${V} "${variable}" is officially deprecated`) + } + deprecatedVariables.push(variable) + } + } + + for (const removedVariable of removed) { + if (!deprecatedVariables.includes(removedVariable)) { + const error = `"${removedVariable}" has been removed, but was not listed in versionDeprecations['${currentVersion}']` + errors.push(error) + console.log(`${X} ${error}`) + } else { + // console.log(`${V} "${removedVariable}" removed and deprecated!`) + } + } + + return errors } function diffLists(before, after) { From b6969f7c203c053c75f3e1da858d944a4d2db21a Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Thu, 31 Oct 2019 16:06:18 -0700 Subject: [PATCH 02/10] improve test-deprecations output --- script/test-deprecations.js | 50 ++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 20 deletions(-) diff --git a/script/test-deprecations.js b/script/test-deprecations.js index 8b987148..942ed2b1 100755 --- a/script/test-deprecations.js +++ b/script/test-deprecations.js @@ -31,10 +31,16 @@ If either check fails, the process exits with an error status (1). } Promise.all([checkSelectorDeprecations(args), checkVariableDeprecations(args)]).then( - (deprecationErrors, variableErrors) => { - const errors = deprecationErrors.concat(variableErrors) + ([deprecationErrors, variableErrors]) => { + const errors = [...deprecationErrors, ...variableErrors] if (errors.length) { + console.log(`\n${errors.length} error${errors.length === 1 ? '' : 's'}:`) + for (const error of errors) { + console.log(`${X} ${error}`) + } process.exit(1) + } else { + console.log(`${V} no errors!`) } } ) @@ -50,27 +56,30 @@ async function checkSelectorDeprecations(options = {}) { const {changed, added, removed} = diffLists(remote.selectors.values, local.selectors.values) if (changed === 0) { - console.log(`no selectors added or removed in bundle "${bundle}"`) - return + console.log(`${I} no selectors changed in bundle "${bundle}" (${version} -> ${currentVersion})`) + // return } const deprecations = versionDeprecations[currentVersion] || [] const deprecatedSelectors = deprecations.reduce((list, deprecation) => list.concat(deprecation.selectors), []) - console.log(`${I} ${removed.length} selectors removed locally (compared with ${version})`) - console.log(`${I} ${deprecatedSelectors.length} selectors deprecated in v${currentVersion}`) + if (removed.length) { + console.log(`${I} ${removed.length} selectors removed locally (compared with ${version})`) + } + if (deprecatedSelectors.length) { + console.log(`${I} ${deprecatedSelectors.length} selectors to be deprecated in ${currentVersion}`) + } if (added.length) { console.log(`${I} ${added.length} selectors added`) } const errors = [] - for (const deprecation of deprecations) { - for (const selector of deprecation.selectors) { + for (const {selectors = []} of deprecations) { + for (const selector of selectors) { if (!removed.includes(selector)) { const error = `"${selector}" deprecated, but not removed` errors.push(error) - console.log(`${X} ${error}`) } else { - console.log(`${V} "${selector}" is officially deprecated`) + console.log(`${V} selector "${selector}" is officially deprecated`) } deprecatedSelectors.push(selector) } @@ -80,7 +89,6 @@ async function checkSelectorDeprecations(options = {}) { if (!deprecatedSelectors.includes(removedSelector)) { const error = `"${removedSelector}" has been removed, but was not listed in versionDeprecations['${currentVersion}']` errors.push(error) - console.log(`${X} ${error}`) } else { // console.log(`${V} "${removedSelector}" removed and deprecated!`) } @@ -100,27 +108,30 @@ async function checkVariableDeprecations(options = {}) { const {changed, added, removed} = diffLists(Object.keys(remote), Object.keys(local)) if (changed === 0) { - console.log(`no variables added or removed in version ${currentVersion}`) - return + console.log(`${I} no variables changed (${version} -> ${currentVersion})`) + // return } const deprecations = versionDeprecations[currentVersion] || [] const deprecatedVariables = deprecations.reduce((list, deprecation) => list.concat(deprecation.variables), []) - console.log(`${I} ${removed.length} variables removed locally (compared with ${version})`) - console.log(`${I} ${deprecatedVariables.length} variables deprecated in v${currentVersion}`) + if (removed.length) { + console.log(`${I} ${removed.length} variables removed locally (compared with ${version})`) + } + if (deprecatedVariables.length) { + console.log(`${I} ${deprecatedVariables.length} variables to be deprecated in ${currentVersion}`) + } if (added.length) { console.log(`${I} ${added.length} variables added`) } const errors = [] - for (const deprecation of deprecations) { - for (const variable of deprecation.variables) { + for (const {variables = []} of deprecations) { + for (const variable of variables) { if (!removed.includes(variable)) { const error = `variable "${variable}" deprecated, but not removed` errors.push(error) - console.log(`${X} ${error}`) } else { - console.log(`${V} "${variable}" is officially deprecated`) + console.log(`${V} variable "${variable}" is officially deprecated`) } deprecatedVariables.push(variable) } @@ -130,7 +141,6 @@ async function checkVariableDeprecations(options = {}) { if (!deprecatedVariables.includes(removedVariable)) { const error = `"${removedVariable}" has been removed, but was not listed in versionDeprecations['${currentVersion}']` errors.push(error) - console.log(`${X} ${error}`) } else { // console.log(`${V} "${removedVariable}" removed and deprecated!`) } From e57d1c02642b044a78be19d41f5458f39a22f52b Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Thu, 31 Oct 2019 16:17:00 -0700 Subject: [PATCH 03/10] fix placement of TODO deprecation for .UnderlineNav-item.selected --- src/navigation/underline-nav.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/navigation/underline-nav.scss b/src/navigation/underline-nav.scss index ed812591..576d1f1a 100644 --- a/src/navigation/underline-nav.scss +++ b/src/navigation/underline-nav.scss @@ -31,7 +31,8 @@ } } - &.selected, // TODO@14.0.0: remove &.selected + // TODO@14.0.0: remove &.selected + &.selected, &[role=tab][aria-selected=true], &[aria-current] { font-weight: $font-weight-bold; From bca8a59b2b2772725ec8448b46e3107761c27633 Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Thu, 31 Oct 2019 16:18:14 -0700 Subject: [PATCH 04/10] BREAKING: remove .UnderlineNav-item.selected --- src/navigation/underline-nav.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/navigation/underline-nav.scss b/src/navigation/underline-nav.scss index 576d1f1a..3b740370 100644 --- a/src/navigation/underline-nav.scss +++ b/src/navigation/underline-nav.scss @@ -31,8 +31,6 @@ } } - // TODO@14.0.0: remove &.selected - &.selected, &[role=tab][aria-selected=true], &[aria-current] { font-weight: $font-weight-bold; From 61ca4f89eef62ddf12cb7a2ad962c60d766c452c Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Thu, 31 Oct 2019 16:20:15 -0700 Subject: [PATCH 05/10] add deprecation for .UnderlineNav-item.selected --- deprecations.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deprecations.js b/deprecations.js index d8841a08..592f7796 100644 --- a/deprecations.js +++ b/deprecations.js @@ -5,6 +5,10 @@ */ const versionDeprecations = { '14.0.0': [ + { + selectors: ['.UnderlineNav-item.selected', '.UnderlineNav-item.selected .UnderlineNav-octicon'], + message: `Please use aria-selected="true" to indicate the selected state of an UnderlineNav item.` + }, { variables: ['$status-pending'], message: `This variable is deprecated.` From e6c6ae10f3554135d964c9f6125d0d88278208ca Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Fri, 1 Nov 2019 11:05:46 -0700 Subject: [PATCH 06/10] write variables object to data/deprecations.json --- script/dist.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/script/dist.js b/script/dist.js index fe89e9f0..d3d5b6d9 100755 --- a/script/dist.js +++ b/script/dist.js @@ -88,15 +88,20 @@ function getPathName(path) { } function writeDeprecationData() { - const {versionDeprecations, selectorDeprecations} = require('../deprecations') + const {versionDeprecations, selectorDeprecations, variableDeprecations} = require('../deprecations') const data = { versions: versionDeprecations, - selectors: Array.from(selectorDeprecations.entries()).reduce((obj, [selector, deprecation]) => { - obj[selector] = deprecation + selectors: mapToObject(selectorDeprecations), + variables: mapToObject(variableDeprecations) + } + return writeFile(join(outDir, 'deprecations.json'), JSON.stringify(data, null, 2)) + + function mapToObject(map) { + return Array.from(map.entries()).reduce((obj, [key, value]) => { + obj[key] = value return obj }, {}) } - return writeFile(join(outDir, 'deprecations.json'), JSON.stringify(data, null, 2)) } if (require.main === module) { From 62a5766ebf3e6f0060ef4b1856c2992b2e53c8b2 Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Fri, 1 Nov 2019 11:14:53 -0700 Subject: [PATCH 07/10] Update docs for variable deprecation data --- docs/content/tools/deprecations.md | 58 +++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/docs/content/tools/deprecations.md b/docs/content/tools/deprecations.md index 1f28a910..9c8f370a 100644 --- a/docs/content/tools/deprecations.md +++ b/docs/content/tools/deprecations.md @@ -2,28 +2,25 @@ title: Deprecation data --- -As of version 12.7.0, we publish CSS selector deprecation data with -`@primer/css`. You can access the data via the [Node API](#node) or as -[JSON](#json). +As of version 12.7.0, we publish CSS selector and SCSS variable deprecation data (as of 14.0.0) with `@primer/css`. You can access the data via the [Node API](#node) or as [JSON](#json). -Deprecation messages strings may include Markdown so that they can be included -in the [changelog]. - -**Keep in mind that this data includes both active and _planned_ -deprecations.** You can determine whether a CSS selector is deprecated for the -version of `@primer/css` you've installed via the [Node API](#node), or by -comparing the version of a selector deprecation with the installed version in -your own environment. +**Keep in mind that this data includes both active and _planned_ deprecations.** The [Node API](#node) is the best way to determine whether a selector or variable is deprecated for the version of `@primer/css` you've installed. ## JSON The JSON data is available in the unpacked node module's `dist/deprecations.json`, and is an object with the following structure: -* `versions` is an object whose keys are version numbers (e.g. `13.0.0`) and values are deprecation messages: objects with a `selectors` array and a `message`: +* `versions` is an object whose keys are version numbers (e.g. `13.0.0`) and values are deprecation messages, each of which has a `message` string and a `selectors` and/or `variables` array: ```json { "versions": { + "14.0.0": [ + { + "variables": ["$min_tab_size", "$max_tab_size"], + "message": "These variables have been deprecated." + } + ], "13.0.0": [ { "selectors": [".btn-purple"], @@ -33,6 +30,8 @@ The JSON data is available in the unpacked node module's `dist/deprecations.json } } ``` + + Deprecation messages strings may include Markdown so that they can be included in the [changelog]. * `selectors` is an object mapping CSS selectors (e.g. `.btn-purple`) to the version in which they are _or will be_ deprecated: @@ -46,6 +45,20 @@ The JSON data is available in the unpacked node module's `dist/deprecations.json } } ``` + +* `variables` is an object mapping SCSS variables (including the leading `$`, e.g. `$status-pending`) to the version in which they are or will be deprecated: + + ```json + { + "variables": { + "$status-pending": { + "version": "14.0.0", + "message": "This variable is unused in Primer, and is deprecated." + } + } + } + ``` + ## Node @@ -90,6 +103,27 @@ console.log(`Primary buttons are bad? ${isSelectorDeprecated('.btn-primary')}`) // "Primary buttons are bad? false" ``` +### `variableDeprecations` +This is a [Map] object with keys for each SCSS variable mapped to the deprecation info: + +```js +const {selectorDeprecations} = require('@primer/css/deprecations') +console.log(`Will $status-pending be deprecated? ${variableDeprecations.has('$status-pending')}`) +// "Will $status-pending be deprecated? true" +``` + +### `isVariableDeprecated(variable[, version])` +Returns `true` if the named SCSS variable (including the leading `$`) will have been deprecated (removed) _by_ the specified [semver] version. + +```js +const {isVariableDeprecated} = require('@primer/css/deprecations') +console.log(`$status-pending deprecated? ${isVariableDeprecated('$status-pending')}`) +// "$status-pending deprecated? true" +console.log(`$yellow-700 deprecated? ${isVariableDeprecated('$yellow-700')}`) +// "$yellow-700 deprecated false" +``` + + [semver]: https://npm.im/semver [changelog]: https://github.com/primer/css/tree/master/CHANGELOG.md [Map]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map From ade9104565a3b2da03624ae7d4592e9cf877daef Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Fri, 1 Nov 2019 12:46:59 -0700 Subject: [PATCH 08/10] update label guidance for new style docs --- docs/content/getting-started/contributing.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/content/getting-started/contributing.md b/docs/content/getting-started/contributing.md index a0563adc..c15c2e22 100644 --- a/docs/content/getting-started/contributing.md +++ b/docs/content/getting-started/contributing.md @@ -42,7 +42,11 @@ It's usually better to open an issue before investing time in spiking out a new 1. What the pattern is and how it's being used across the site - post screenshots and urls where possible. If you need help identifying where the pattern is being used, call that out here and cc the relevant team and/or cc `@product-design` to help. 2. Why you think a new pattern is needed (this should answer the relevant questions above). 3. If you intend to work on these new styles yourself, let us know what your timeline and next steps are. If you need help and this is a dependency for shipping another project, please make that clear here and what the timeline is. -4. Add the `type: new styles` label, or `type: refactor` where appropriate. +4. Add the appropriate label(s): + - `Type: Enhancement` for new styles + - `Type: Bug Fix` for—you guessed it!—bug fixes + - `Type: Polish` for refactors of existing styles + - `Type: Breaking Change` for any change that [removes CSS selectors or SCSS variables](#removing-styles-and-variables) ### Step 2: Design and build the new styles From beb5a8bebe72b08be33fcd9e91d3857678f85b2d Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Fri, 1 Nov 2019 12:47:16 -0700 Subject: [PATCH 09/10] add deprecation docs in getting-started/contributing --- docs/content/getting-started/contributing.md | 66 ++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/docs/content/getting-started/contributing.md b/docs/content/getting-started/contributing.md index c15c2e22..a42b6b7f 100644 --- a/docs/content/getting-started/contributing.md +++ b/docs/content/getting-started/contributing.md @@ -68,6 +68,67 @@ If you get to this step you've helped contribute to a style guide that many of y Let the [design systems team](https://github.com/github/design-systems) know if we can improve these guidelines and make following this process any easier. + +## Removing styles and variables + +Removing styles and SCSS variables can be a scary. How do you know if the thing you're deleting (or just planning to delete) isn't used in other projects? [Semantic versioning] provides us with an answer: We **don't** know, but we can use "major" version increments (from, say, `13.4.0` to `14.0.0`) to signal that the release includes potentially breaking changes. The rule is simple: + +**Whenever we delete a CSS selector or SCSS variable, we will increment to the next major version.** + +When planning to delete a CSS selector or SCSS variable, you should: + +1. Add a [TODO@version comment](#primer-csstodo) above the line in question: + + ```scss + // TODO@15.0.0: delete $some-unused-var + $some-unused-var: 15px !default; + ``` + +1. Add it to [deprecations.js]: + + ```js + const versionDeprecations = { + '15.0.0': [ + { + variables: ['$some-unused-var'], + message: '$some-unused-var is unused, and has been deprecated.' + } + ] + } + ``` + +We have several checks and tools in place to help us plan, track, and catch both expected and unexpected removals of both CSS selectors and SCSS variables: + +### `deprecations.js` +[This file][deprecations.js] is where we document all of our current and _planned_ CSS selector and SCSS variable deprecations (removals), and is used to generate [deprecation data](../tools/deprecations) for other tools. + +### `script/test-deprecations.js` +[This script][script/test-deprecations.js] compares the CSS stats and variable data between the latest release and the local code, and throws error messages if: + +- A CSS selector has been deleted but was not listed in [deprecations.js] +- A CSS selector listed in [deprecations.js] was _not removed_ in the version it claims to have been deprecated +- An SCSS variable has been deleted but was not listed in [deprecations.js] +- An SCSS variable listed in [deprecations.js] was _not removed_ in the version it claims to have been deprecated + +Run `script/test-deprecation.js --help` for more info and available options. + +### `primer-css/TODO` +[This stylelint rule][lib/stylelint-todo.js] looks for comments in the form: + +```scss +// TODO@: +``` + +and generates an error for each one whose `` is less than or equal to the current version (in `package.json`). You can test this rule for future releases with: + +```sh +PRIMER_VERSION= npx stylelint-only primer-css/TODO -- src +``` + +where `` is the future version you'd like to compare against. Assuming that the correctly formatted comments exist already, violations of this stylelint rule can be used to generate a checklist of lines to remove in a future release. + +See [the deprecation data docs](../tools/deprecations) for more information. + ## Documentation structure - Our documentation site for Primer CSS is built using [Doctocat](https://primer.style/doctocat) and deployed with [Now](https://zeit.co/now). Our site is built from the `docs` folder and uses [MDX](https://mdxjs.com) to render markdown. @@ -101,3 +162,8 @@ Check out Doctocat's [live code](https://primer.style/doctocat/usage/live-code) Primer CSS follows [semantic versioning](http://semver.org/) conventions. This helps others know when a change is a patch, minor, or breaking change. To understand what choice to make, you'll need to understand semver and know if one of the changes shown is a major, minor, or patch. Semver is confusing at first, so I recommend reviewing [semver](http://semver.org/) and/or ask in [#design-systems](https://github.slack.com/archives/design-systems) or and experienced open-source contributor. + +[semantic versioning]: https://semver.org +[script/test-deprecations.js]: https://github.com/primer/css/tree/master/script/test-deprecations.js +[deprecations.js]: https://github.com/primer/css/tree/master/deprecations.js +[lib/stylelint-todo.js]: https://github.com/primer/css/tree/master/lib/stylelint-todo.js From c6be8553582a6496e9ecb39d356ea8ffdb30702b Mon Sep 17 00:00:00 2001 From: Shawn Allen Date: Mon, 4 Nov 2019 14:48:22 -0800 Subject: [PATCH 10/10] fix typo Co-Authored-By: simurai --- docs/content/getting-started/contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/content/getting-started/contributing.md b/docs/content/getting-started/contributing.md index a42b6b7f..ece93dc4 100644 --- a/docs/content/getting-started/contributing.md +++ b/docs/content/getting-started/contributing.md @@ -71,7 +71,7 @@ Let the [design systems team](https://github.com/github/design-systems) know if ## Removing styles and variables -Removing styles and SCSS variables can be a scary. How do you know if the thing you're deleting (or just planning to delete) isn't used in other projects? [Semantic versioning] provides us with an answer: We **don't** know, but we can use "major" version increments (from, say, `13.4.0` to `14.0.0`) to signal that the release includes potentially breaking changes. The rule is simple: +Removing styles and SCSS variables can be scary. How do you know if the thing you're deleting (or just planning to delete) isn't used in other projects? [Semantic versioning] provides us with an answer: We **don't** know, but we can use "major" version increments (from, say, `13.4.0` to `14.0.0`) to signal that the release includes potentially breaking changes. The rule is simple: **Whenever we delete a CSS selector or SCSS variable, we will increment to the next major version.**