Skip to content

Contributing code

Before you dive into contributing code:

Get the code

Clone the repository: git clone ssh://gerrit.wikimedia.org:29418/design/codex

First time using Gerrit?

See the Gerrit tutorial on mediawiki.org for a detailed guide.

Code contribution workflow

Task tracking

Tasks are tracked in Phabricator. We use three different Phabricator workboards, for three different purposes:

  • Codex: used to indicate that a task is related to Codex. We do not track task status here.
  • Design-System-Team: used to triage, sort, prioritize, and refine tasks that the Design System Team and contributors will work on.
  • Design-System-Sprint: find the current sprint on the DST workboard too, starting with "DST-Sprint". The sprint is used to track active works-in-progress from Research > Design > Development > Testing > Release. As a contributor, you are welcome to track your work related to the Design System on this board as well—just remember to keep the task in the appropriate column that reflects its status.

Create or claim a task as soon as you decide to work on it. This will help avoid overlapping duplicate, or out-of-order work. Note that very minor contributions (like build asset updates or icon code optimizations) do not require a corresponding task.

When adding a new component, developing the entire component to completely fulfill design criteria may be too much for a single patch. Consider creating a parent task for that component with sub-tasks for the minimum viable solution and additional features.

Component addition process

The following list represents the full process of adding a component, from conception as a task to a fully functional and documented component.

TIP

These steps may be completed by a number of different contributors—a single developer does not need complete all of these steps on their own. Read the code contribution pathways section below for more info on different levels of contribution.

  1. Create a new component task in Phabricator. New Codex components must have a corresponding task in Phabricator. Use this new component task template to create the task, filling in as much information as you can.
  2. Work with the design team to create a scope of work. Typically, a designated designer and developer work together to create a scope for the minimum viable product (MVP) version of the component. New tasks will be added for any other features that will be designed and developed after the MVP scope is complete.
  3. Await the Figma spec sheet. Before a component can be implemented in Codex, the design team will create a specification sheet for it in the Codex components Figma library. A new component task is considered ready for development only once design specifications have been linked in the component task.
  4. Ready for development. Once design specifications have been shared and the task has been refined by members of the Design System Team, the task will be moved to the "Codex Component Backlog" column on the Design-System-Team workboard. This indicates that implementation work can begin. You can assign the task to yourself and move it to the 'In Development' column on the current Design-System-Sprint board linked from the DST workboard.
  5. Build the component. Create the Vue component, applying the design tokens noted in the design specification. Visit writing styles for more details. As you build the component, add a simple demo to the Vite Sandbox for testing during development.
  6. Test the component. Write unit tests for the new code. Visit the unit tests section for more details.
  7. Demo the component. Create component demos in VitePress, following the specifications provided in the task and existing examples from other component demo pages. Visit the component demos section for more details. Also add a simple demo of the component to the Vite Sandbox, if you didn't already do this during development.
  8. Handle feedback from design and product. Relevant designers and product managers will review the new component, provide feedback, and eventually sign-off on the component. Developers will respond to the feedback and implement any necessary changes, or open tasks so those changes can be done in the future.

Code contribution pathways

There are various options for contributing code for a new component based on the contributor's experience with our technologies and available time.

To be ready for public consumption, a component must fully implement the design spec and include unit tests, a simple demo in the Vite sandbox, and a demo page with a suite of comprehensive demos. However, these items do not all need to be included at once. Components that are in development but are not yet ready for public use are considered "work in progress" or "WIP" components. Our WIP components system allows submission of partially-completed components, which are not yet included in the main library distribution or on the live Codex docs site.

Here are some options based on different parts of the development process:

  • Component completeness: You can build a component that implements every part of the design spec, or you can build part of a new component and submit it as a WIP component. More features can be added in subsequent patches by you or other developers.
  • Unit tests: You can submit a new component without writing unit tests (although we do encourage at least adding snapshot tests), or you can help write unit tests for someone else's WIP component.
  • Component demos: While a simple implementation of a component in the Vite Sandbox is required for all components, a full component demo page can be added later. Our component demo system is very specific to this project, so learning it might take more time than a contributor has to offer. You can submit a WIP component without adding a demo page, or you can help add a demo page for someone else's WIP component.
  • Design/product feedback: While the original implementer of a new component can respond to feedback if they have the time and inclination, feedback can be handled by other developers, or added to the backlog to be completed later.

If you have questions about how you can contribute to a new component, or a proposal for how you would like to contribute to one, please add a comment to that component's Phabricator task and we will help develop a plan for who will do which parts of the component development process.

Patch requirements

Patches for new components should include all of the following. Patches that introduce changes should include or update the following as needed.

  • Commit message: The first line of the commit message must be prefixed by the component name (Button, Icon) or the type of work (build, tests, styles, docs, sandbox, etc.), e.g. docs: Add more docs. If the commit covers multiple things, include them in a comma-delineated list, e.g. styles, docs, Button: Fix Button styles and document new convention. Deprecating and breaking changes are prefixed with [DEPRECATING CHANGE] or [BREAKING CHANGE], e.g. [BREAKING CHANGE] Button: Remove primary button functionality. In general, Codex follows the MediaWiki commit message guidelines.
  • Documentation: Each component should have a docblock describing what the component is and the basics of how it works. Props, slots, and events should be documented in the code. If a component would benefit from additional documentation to communicate how it can be used, include it on the demo page.
  • Vite Sandbox demo: Each component should have a simple demo in the Vite Sandbox for testing the basic functionality of the component locally and within MediaWiki.
  • Jest snapshots: Include snapshots for all variations of props and slots.
  • Unit tests (not required for WIP components): Attempt to meet the established coverage threshold, which will be calculated and output in the command line interface when you run tests.
  • Demos (not required for WIP components): Each component should have a demo page on the VitePress site that shows realistic examples of different variations and uses of the component.

Every time a new version of Codex is released, the CHANGELOG.md file in the root directory is updated with all the commit message subjects since the last release – separated in sections titled "Breaking Changes", "Deprecating Changes", "Features", "Styles", "Code" and "Documentation".

A breaking change is a change that requires some code using Codex to be updated in order to keep working, e.g. removing or renaming a component or function. Each breaking change must be preceded by a deprecating change at least a minor version before. A deprecating change warns when using the functionality that is about to be removed, and provides an alternative where applicable. The breaking change is then released in the next major or minor version after.

Code review

The Codex library adheres to the Design System Team's general process for code review. Please review this process before submitting or reviewing a patch for the first time.

Development basics

Requirements

  • Node: see .nvmrc in the root of the repository. To install and use the required version of Node, run nvm install "$(<.nvmrc)" then nvm use in the root of the repository.
  • NPM: v7.21 or greater is required to support workspaces; it is not included by default in older versions of Node (prior to v15) and will need to be upgraded manually.

Workspaces

The Codex codebase is configured as a monorepo using NPM. Sub-projects are defined in the packages/ directory with their own package.json and an appropriate name.

To run a command in a specific workspace, do the following:

bash
# Run the "build" command in the "@wikimedia/codex" workspace
npm run build --workspace @wikimedia/codex

# Install a dependency for one workspace
npm install vite --workspace @wikimedia/codex --save-dev

# This can be shortened to -w
npm install vue -w @wikimedia/codex --save-peer

To run a command for all workspaces, do this instead:

bash
# Run the "test" command in all workspaces
npm run test --workspaces

# This is equivalent
npm run test -ws

Note that the lint command is global; it has to be run in the root directory. Running npm test in the root directory will lint everything and run the tests in all workspaces.

Directory structure

Each component has its own subdirectory in packages/codex/src/components, containing the Vue single file component and unit tests.

Functions designed for use with the composition API ("composables") are in src/composables. Composables should have a name starting with use, e.g. useModelWrapper. Each composable should be in its own file, with the same name as the composable function, e.g. src/composables/useModelWrapper.ts.

There are some special files at the top level of the src/ directory:

  • lib.ts: The main entry point for the library. Only things that are exported here are accessible to external users of the library.
  • types.ts: Contains type definitions that are shared across multiple components, or need to be exported. This file should not export any functions or constants, only types.
  • constants.ts: Contains constants that need to be exported or that serve as the basis for types.
  • utils.ts: Utility functions shared across components, or for use with the types or constants in types.ts or constants.ts.

Linting

Codex uses the following lint tools:

We use the Wikimedia coding conventions, and to enforce those we use eslint-config-wikimedia and stylelint-config-wikimedia as the starting point for our linter configuration. For Vue files, the Wikimedia coding conventions follow the Vue style guide closely. This means that HTML-like tags in Vue templates sometimes don't follow the same rules as real HTML: for example, an empty div tag is <div /> not <div></div>.

In addition to those conventions, Codex-specific rules are enforced. These include:

  • For most CSS properties, design tokens must be used, and raw values are forbidden. (For example, padding-top: 16px; is disallowed, use padding-top: @spacing-100; instead.)
  • Every .js, .ts and .vue file must be captured by the include paths in one of the tsconfig.json files, otherwise typescript-eslint can't run properly.

Running the linters

You can use the following commands to run some or all of the lint checks. All of these commands must be run in the root directory

  • All lint checks: npm run lint (this runs all of the commands listed below)
  • ESLint: npm run lint:eslint
  • Stylelint: npm run lint:stylelint
  • svglint: npm run -w @wikimedia/codex-icons lint:svg
  • TypeScript checks: npm run -w PACKAGENAME lint:ts, where PACKAGENAME is one of @wikimedia/codex, @wikimedia/codex-design-tokens, @wikimedia/codex-icons or codex-docs

You should also consider setting up your IDE to run ESLint, Stylelint and TypeScript on the fly, to catch linter violations in real time.

Disabling lint rules

The lint rules produce the desired outcome the vast majority of the time, but sometimes disabling a lint rule is necessary. The preferred way of doing this is with a disable-next-line comment that identifies the name of the rule to be disabled, like this:

js
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
input.value!.click();
template
<!-- eslint-disable-next-line vue/no-v-html -->
<div v-html="foo" />
css
/* stylelint-disable-next-line plugin/no-unsupported-browser-features */
appearance: none;

Sometimes, a rule might have to be disabled for a block of multiple lines, either because it appears multiple times in a row or because it's not possible to place a comment directly above the offending line. To do this, use a disable comment for the specific rule, and then an enable comment at the end of the block re-enabling the rule.

js
/* eslint-disable no-multi-spaces */
const MODES = [
  '',             // default mode
  'rtl',          // RTL stylesheet
  'legacy',       // Legacy (14px base) stylesheet
  'legacy-rtl'    // RTL 14px base stylesheet
];
/* eslint-enable no-multi-spaces */
template
<!-- eslint-disable vue/no-v-html -->
<g
  v-if="iconSvg"
  v-html="iconSvg"
/>
<!-- eslint-enable vue/no-v-html -->
css
/* stylelint-disable plugin/no-unsupported-browser-features */
-webkit-appearance: none;
-moz-appearance: textfield;
/* stylelint-enable plugin/no-unsupported-browser-features */

Some rules are disabled for an entire repository, workspace or directory in the relevant .eslintrc.json or .stylelintrc.json file. This should not be done lightly, and is only appropriate when the rule is not useful for a structural reason. For example, we disable the no-unsafe group of rules from typescript-eslint in Vue files, because they cause lots of false positives in Vue code.

IDE setup

If you work on Codex using an editor not listed here, please feel free to submit a patch with suggested configurations for optimal development for that editor!

Visual Studio Code

  1. Install extensions: The Codex repository contains a list of recommended extensions for development in Visual Studio Code (VS Code). These extensions are defined in vscode/extensions.json. When you open Codex in VS Code, you should be prompted with a message to install recommended extensions. You can also go to the Extensions tab and search for '@recommended', then install the extensions you want.
  2. Configure settings for extensions: Configure Stylelint to check your styles, rather than VS Code's built-in validators. Add the following to your .vscode/settings.json file:
json
{
	"css.validate": false,
	"less.validate": false,
	"stylelint.snippet": [
		"css",
		"less",
		"postcss",
		"vue"
	],
	"stylelint.validate": [
		"css",
		"less",
		"postcss",
		"vue"
	]
}

Configure Rewrap to wrap at 100 characters column:

json
{
	"rewrap.wrappingColumn": 100
}