From 82a0c94414d0419e23eda6eb668fa08f7bfdbaaa Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Tue, 16 Jun 2026 18:51:45 +0100
Subject: [PATCH 01/10] gerrit patch pull
---
.../references/gerrit-patch-trial.md | 49 ++++
package.json | 4 +-
scripts/patch-codex.mjs | 240 ++++++++++++++++++
.../example-codex-kitchen-sink/index.vue | 181 +++++++++++++
4 files changed, 473 insertions(+), 1 deletion(-)
create mode 100644 .agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md
create mode 100644 scripts/patch-codex.mjs
create mode 100644 src/prototypes/example-codex-kitchen-sink/index.vue
diff --git a/.agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md b/.agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md
new file mode 100644
index 0000000..39782e7
--- /dev/null
+++ b/.agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md
@@ -0,0 +1,49 @@
+# Testing a Codex Gerrit patch in ProtoWiki
+
+Use this when you want to trial an unmerged `design/codex` change in ProtoWiki without publishing a package.
+
+## Commands
+
+Apply the current patchset for a change:
+
+```bash
+npm run patch-codex -- https://gerrit.wikimedia.org/r/c/design/codex/+/1288878
+```
+
+The command:
+
+- reads Gerrit metadata for the given change
+- fetches the current patchset into a local cache checkout
+- builds `design/codex`
+- packs and installs local tarballs for all three packages:
+ - `@wikimedia/codex`
+ - `@wikimedia/codex-design-tokens`
+ - `@wikimedia/codex-icons`
+
+Reset to normal registry-resolved packages:
+
+```bash
+npm run patch-codex:reset
+```
+
+## Why all three packages are patched together
+
+ProtoWiki consumes all three Codex packages directly, and `@wikimedia/codex` depends on matching icon/token outputs. Keeping them in lockstep avoids mixed-version visual bugs.
+
+## Suggested smoke-test routes
+
+After applying a patch, run `npm run dev` and inspect:
+
+- `/`
+- `/template-homepage`
+- `/template-homepage/suggested-edits`
+- `/example-event-worklist`
+- `/template-article-live`
+- `/example-codex-kitchen-sink`
+
+Focus on light/dark theme and desktop/mobile skin states.
+
+## Notes
+
+- The cache checkout lives under your system temp directory (`$TMPDIR/protowiki-codex-gerrit-cache` on macOS).
+- This workflow uses `npm install --no-save`, so it does not intentionally rewrite dependency ranges in `package.json`.
diff --git a/package.json b/package.json
index 71244ba..c04335e 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,9 @@
"type-check": "vue-tsc --noEmit",
"format": "prettier --write \"src/**/*.{ts,vue,css}\"",
"lint": "eslint . --ext .ts,.vue",
- "snapshot:wiki-skins": "bash scripts/snapshot-wiki-skins.sh"
+ "snapshot:wiki-skins": "bash scripts/snapshot-wiki-skins.sh",
+ "patch-codex": "node scripts/patch-codex.mjs",
+ "patch-codex:reset": "node scripts/patch-codex.mjs --reset"
},
"dependencies": {
"@wikimedia/codex": "^2.6.0",
diff --git a/scripts/patch-codex.mjs b/scripts/patch-codex.mjs
new file mode 100644
index 0000000..b0a12cb
--- /dev/null
+++ b/scripts/patch-codex.mjs
@@ -0,0 +1,240 @@
+#!/usr/bin/env node
+import fs from 'node:fs'
+import os from 'node:os'
+import path from 'node:path'
+import { fileURLToPath } from 'node:url'
+import { spawnSync } from 'node:child_process'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+const ROOT = path.resolve(__dirname, '..')
+const CACHE_ROOT = path.join(os.tmpdir(), 'protowiki-codex-gerrit-cache')
+const CODEX_REPO_DIR = path.join(CACHE_ROOT, 'design-codex')
+const PACK_DIR = path.join(CACHE_ROOT, 'packs')
+const GERRIT_REPO = 'https://gerrit.wikimedia.org/r/design/codex'
+
+const CODEX_PACKAGES = [
+ '@wikimedia/codex',
+ '@wikimedia/codex-design-tokens',
+ '@wikimedia/codex-icons',
+]
+
+const PACKAGE_DIRS = new Map([
+ ['@wikimedia/codex', 'packages/codex'],
+ ['@wikimedia/codex-design-tokens', 'packages/codex-design-tokens'],
+ ['@wikimedia/codex-icons', 'packages/codex-icons'],
+])
+
+function run(command, args, opts = {}) {
+ const result = spawnSync(command, args, {
+ cwd: opts.cwd ?? ROOT,
+ stdio: 'pipe',
+ encoding: 'utf8',
+ })
+
+ if (result.status !== 0) {
+ const details = [result.stdout, result.stderr].filter(Boolean).join('\n')
+ throw new Error(`Command failed: ${command} ${args.join(' ')}\n${details || '(no output)'}`)
+ }
+
+ return (result.stdout ?? '').trim()
+}
+
+function readRootPackageJson() {
+ const packageJsonPath = path.join(ROOT, 'package.json')
+ return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
+}
+
+function parseChangeNumber(input) {
+ if (!input) {
+ throw new Error('Missing Gerrit URL or change number.')
+ }
+ const trimmed = input.trim()
+ if (/^\d+$/.test(trimmed)) {
+ return trimmed
+ }
+
+ const url = new URL(trimmed)
+ const slashPlusMatch = url.pathname.match(/\/\+\/(\d+)(?:\/)?$/)
+ if (slashPlusMatch) {
+ return slashPlusMatch[1]
+ }
+
+ const cPathMatch = url.pathname.match(/\/c\/[^/]+\/[^/]+\/\+\/(\d+)(?:\/)?$/)
+ if (cPathMatch) {
+ return cPathMatch[1]
+ }
+
+ throw new Error(`Could not parse Gerrit change number from: ${input}`)
+}
+
+function stripXssiPrefix(body) {
+ const lines = body.split('\n')
+ if (lines[0] === `)]}'`) {
+ return lines.slice(1).join('\n')
+ }
+ return body
+}
+
+async function fetchJson(url) {
+ const response = await fetch(url)
+ if (!response.ok) {
+ throw new Error(`Request failed (${response.status}): ${url}`)
+ }
+ const text = await response.text()
+ return JSON.parse(stripXssiPrefix(text))
+}
+
+function ensureRepo() {
+ fs.mkdirSync(CACHE_ROOT, { recursive: true })
+ if (!fs.existsSync(CODEX_REPO_DIR)) {
+ console.log(`Cloning design/codex into ${CODEX_REPO_DIR}`)
+ run('git', ['clone', '--filter=blob:none', GERRIT_REPO, CODEX_REPO_DIR], { cwd: CACHE_ROOT })
+ } else {
+ run('git', ['remote', 'set-url', 'origin', GERRIT_REPO], { cwd: CODEX_REPO_DIR })
+ }
+}
+
+function ensureDependenciesInstalled() {
+ console.log('Installing Codex dependencies')
+ try {
+ run('npm', ['install'], { cwd: CODEX_REPO_DIR })
+ } catch (error) {
+ console.warn('Default npm install failed; retrying with --ignore-scripts')
+ run('npm', ['install', '--ignore-scripts'], { cwd: CODEX_REPO_DIR })
+ console.warn(
+ 'Installed with --ignore-scripts; this bypasses non-essential postinstall hooks in local tooling.',
+ )
+ if (!(error instanceof Error)) {
+ throw error
+ }
+ }
+}
+
+function buildCodex() {
+ console.log('Building design/codex packages used by ProtoWiki')
+ for (const packageName of CODEX_PACKAGES) {
+ run('npm', ['run', 'build', '--workspace', packageName], { cwd: CODEX_REPO_DIR })
+ }
+}
+
+function packOne(packageName) {
+ const relDir = PACKAGE_DIRS.get(packageName)
+ if (!relDir) {
+ throw new Error(`No package directory mapping for ${packageName}`)
+ }
+ const packageDir = path.join(CODEX_REPO_DIR, relDir)
+ fs.mkdirSync(PACK_DIR, { recursive: true })
+ if (packageName === '@wikimedia/codex-design-tokens') {
+ const distDir = path.join(packageDir, 'dist')
+ for (const entry of fs.readdirSync(distDir)) {
+ if (!entry.startsWith('theme-')) continue
+ const src = path.join(distDir, entry)
+ const dest = path.join(packageDir, entry)
+ fs.copyFileSync(src, dest)
+ }
+ }
+ const packOutput = run('npm', ['pack', '--pack-destination', PACK_DIR], { cwd: packageDir })
+ const tarballName = packOutput
+ .split('\n')
+ .map((line) => line.trim())
+ .filter((line) => line.endsWith('.tgz'))
+ .at(-1)
+ if (!tarballName) {
+ throw new Error(`Unable to find tarball name in npm pack output for ${packageName}`)
+ }
+ return path.join(PACK_DIR, tarballName)
+}
+
+function installTarballs(tarballs) {
+ console.log('Installing local Codex tarballs into ProtoWiki')
+ run('npm', ['install', '--no-save', ...tarballs], { cwd: ROOT })
+}
+
+function resetToRegistry() {
+ const packageJson = readRootPackageJson()
+ const desired = CODEX_PACKAGES.map((name) => {
+ const range = packageJson.dependencies?.[name]
+ if (!range) {
+ throw new Error(`Missing dependency range in package.json: ${name}`)
+ }
+ return `${name}@${range}`
+ })
+
+ console.log('Restoring Codex packages from npm registry')
+ run('npm', ['install', '--no-save', ...desired], { cwd: ROOT })
+ printInstalledVersions('reset complete')
+}
+
+function printInstalledVersions(context) {
+ const lines = CODEX_PACKAGES.map((name) => {
+ const p = path.join(ROOT, 'node_modules', name, 'package.json')
+ if (!fs.existsSync(p)) {
+ return `${name}: not installed`
+ }
+ const v = JSON.parse(fs.readFileSync(p, 'utf8')).version
+ return `${name}: ${v}`
+ })
+ console.log(`\nCodex package state (${context}):`)
+ for (const line of lines) {
+ console.log(`- ${line}`)
+ }
+}
+
+function computePatchRef(changeNumber, patchsetNumber) {
+ const twoDigit = changeNumber.slice(-2).padStart(2, '0')
+ return `refs/changes/${twoDigit}/${changeNumber}/${patchsetNumber}`
+}
+
+async function applyChange(changeInput) {
+ const changeNumber = parseChangeNumber(changeInput)
+ console.log(`Fetching Gerrit change ${changeNumber}`)
+
+ const detailUrl = `https://gerrit.wikimedia.org/r/changes/design%2Fcodex~${changeNumber}/detail`
+ const detail = await fetchJson(detailUrl)
+
+ const patchset = detail.current_revision_number
+ if (!patchset) {
+ throw new Error('Gerrit response did not include current_revision_number')
+ }
+
+ const ref = computePatchRef(changeNumber, String(patchset))
+ console.log(`Current patchset: ${patchset} (${ref})`)
+
+ ensureRepo()
+ run('git', ['fetch', 'origin', ref], { cwd: CODEX_REPO_DIR })
+ run('git', ['checkout', '--detach', 'FETCH_HEAD'], { cwd: CODEX_REPO_DIR })
+
+ ensureDependenciesInstalled()
+ buildCodex()
+
+ const tarballs = CODEX_PACKAGES.map(packOne)
+ installTarballs(tarballs)
+
+ const revision = detail.current_revision ?? '(unknown revision)'
+ printInstalledVersions(`patched from Gerrit ${changeNumber}`)
+ console.log(`\nApplied change ${changeNumber} at revision ${revision}.`)
+ console.log('To restore registry versions: npm run patch-codex:reset')
+}
+
+async function main() {
+ const [, , firstArg, secondArg] = process.argv
+ if (firstArg === '--reset') {
+ resetToRegistry()
+ return
+ }
+
+ const changeInput = secondArg && firstArg === '--change' ? secondArg : firstArg
+ if (!changeInput) {
+ console.log('Usage:')
+ console.log(' npm run patch-codex -- ')
+ console.log(' npm run patch-codex:reset')
+ process.exit(1)
+ }
+
+ await applyChange(changeInput)
+}
+
+main().catch((error) => {
+ console.error(error instanceof Error ? error.message : String(error))
+ process.exit(1)
+})
diff --git a/src/prototypes/example-codex-kitchen-sink/index.vue b/src/prototypes/example-codex-kitchen-sink/index.vue
new file mode 100644
index 0000000..d3cc2ed
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/index.vue
@@ -0,0 +1,181 @@
+
+
+
+
+
+
+
+ Inputs and compact controls
+
+ Use this block to compare rounded corners across input fields and buttons.
+
+
+
+ Search article
+
+
+
+
+ Project
+
+
+
+
+ Selected action: {{ selectedAction }}
+
+
+
+ Long one-line button-group labels
+
+ This row intentionally stresses first/last button edge rounding.
+
+
+
+
+
+
+ Toggle disabled third button
+
+
+ Open Dialog
+
+
+ Selected filter: {{ selectedFilter }}
+
+
+
+
+
+ Compare button radius in default, quiet, and progressive states while this dialog is open.
+
+
+
+
+ Cancel
+
+
+ Add item
+
+
+
+
+
+
+
+
From 8d34548c74d9aae97b633f9b496ef1836a558f4e Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Wed, 17 Jun 2026 12:09:24 +0100
Subject: [PATCH 02/10] maybe gerrit patch
---
.../references/gerrit-patch-trial.md | 106 ++-
package-lock.json | 16 +-
package.json | 2 +
patches/codex/manifest.json | 710 ++++++++++++++++++
...t__theme-wikimedia-ui-mode-dark.json.patch | 48 ++
...__theme-wikimedia-ui-mode-large.json.patch | 48 ++
...__theme-wikimedia-ui-mode-small.json.patch | 48 ++
...theme-wikimedia-ui-mode-x-large.json.patch | 48 ++
...tokens__dist__theme-wikimedia-ui.css.patch | 15 +
...-tokens__dist__theme-wikimedia-ui.js.patch | 15 +
...okens__dist__theme-wikimedia-ui.json.patch | 48 ++
...okens__dist__theme-wikimedia-ui.less.patch | 15 +
...okens__dist__theme-wikimedia-ui.scss.patch | 15 +
...s__theme-wikimedia-ui-mode-dark.json.patch | 48 ++
...__theme-wikimedia-ui-mode-large.json.patch | 48 ++
...__theme-wikimedia-ui-mode-small.json.patch | 48 ++
...theme-wikimedia-ui-mode-x-large.json.patch | 48 ++
...esign-tokens__theme-wikimedia-ui.css.patch | 15 +
...design-tokens__theme-wikimedia-ui.js.patch | 15 +
...sign-tokens__theme-wikimedia-ui.json.patch | 48 ++
...sign-tokens__theme-wikimedia-ui.less.patch | 15 +
...sign-tokens__theme-wikimedia-ui.scss.patch | 15 +
...a__codex__dist__codex.style-bidi.css.patch | 320 ++++++++
...ia__codex__dist__codex.style-rtl.css.patch | 308 ++++++++
...imedia__codex__dist__codex.style.css.patch | 308 ++++++++
...dist__modules__CdxAccordion-bidi.css.patch | 25 +
..._dist__modules__CdxAccordion-rtl.css.patch | 25 +
...dex__dist__modules__CdxAccordion.css.patch | 25 +
...x__dist__modules__CdxButton-bidi.css.patch | 15 +
...ex__dist__modules__CdxButton-rtl.css.patch | 15 +
..._codex__dist__modules__CdxButton.css.patch | 15 +
...st__modules__CdxButtonGroup-bidi.css.patch | 36 +
...ist__modules__CdxButtonGroup-rtl.css.patch | 28 +
...x__dist__modules__CdxButtonGroup.css.patch | 28 +
...dex__dist__modules__CdxCard-bidi.css.patch | 15 +
...odex__dist__modules__CdxCard-rtl.css.patch | 15 +
...a__codex__dist__modules__CdxCard.css.patch | 15 +
..._dist__modules__CdxCheckbox-bidi.css.patch | 15 +
...__dist__modules__CdxCheckbox-rtl.css.patch | 15 +
...odex__dist__modules__CdxCheckbox.css.patch | 15 +
...dist__modules__CdxChipInput-bidi.css.patch | 15 +
..._dist__modules__CdxChipInput-rtl.css.patch | 15 +
...dex__dist__modules__CdxChipInput.css.patch | 15 +
...x__dist__modules__CdxDialog-bidi.css.patch | 15 +
...ex__dist__modules__CdxDialog-rtl.css.patch | 15 +
..._codex__dist__modules__CdxDialog.css.patch | 15 +
...ex__dist__modules__CdxImage-bidi.css.patch | 26 +
...dex__dist__modules__CdxImage-rtl.css.patch | 26 +
...__codex__dist__modules__CdxImage.css.patch | 26 +
...dex__dist__modules__CdxMenu-bidi.css.patch | 15 +
...odex__dist__modules__CdxMenu-rtl.css.patch | 15 +
...a__codex__dist__modules__CdxMenu.css.patch | 15 +
...__dist__modules__CdxMessage-bidi.css.patch | 15 +
...x__dist__modules__CdxMessage-rtl.css.patch | 15 +
...codex__dist__modules__CdxMessage.css.patch | 15 +
...__dist__modules__CdxPopover-bidi.css.patch | 30 +
...x__dist__modules__CdxPopover-rtl.css.patch | 26 +
...codex__dist__modules__CdxPopover.css.patch | 26 +
...st__modules__CdxProgressBar-bidi.css.patch | 15 +
...ist__modules__CdxProgressBar-rtl.css.patch | 15 +
...x__dist__modules__CdxProgressBar.css.patch | 15 +
...st__modules__CdxSearchInput-bidi.css.patch | 15 +
...ist__modules__CdxSearchInput-rtl.css.patch | 15 +
...x__dist__modules__CdxSearchInput.css.patch | 15 +
...x__dist__modules__CdxSelect-bidi.css.patch | 26 +
...ex__dist__modules__CdxSelect-rtl.css.patch | 26 +
..._codex__dist__modules__CdxSelect.css.patch | 26 +
...ex__dist__modules__CdxTable-bidi.css.patch | 15 +
...dex__dist__modules__CdxTable-rtl.css.patch | 15 +
...__codex__dist__modules__CdxTable.css.patch | 15 +
...dex__dist__modules__CdxTabs-bidi.css.patch | 17 +
...odex__dist__modules__CdxTabs-rtl.css.patch | 17 +
...a__codex__dist__modules__CdxTabs.css.patch | 17 +
..._dist__modules__CdxTextArea-bidi.css.patch | 15 +
...__dist__modules__CdxTextArea-rtl.css.patch | 15 +
...odex__dist__modules__CdxTextArea.css.patch | 15 +
...dist__modules__CdxTextInput-bidi.css.patch | 15 +
..._dist__modules__CdxTextInput-rtl.css.patch | 15 +
...dex__dist__modules__CdxTextInput.css.patch | 15 +
...dist__modules__CdxThumbnail-bidi.css.patch | 15 +
..._dist__modules__CdxThumbnail-rtl.css.patch | 15 +
...dex__dist__modules__CdxThumbnail.css.patch | 15 +
...t__modules__CdxToggleButton-bidi.css.patch | 15 +
...st__modules__CdxToggleButton-rtl.css.patch | 15 +
...__dist__modules__CdxToggleButton.css.patch | 15 +
...dules__CdxToggleButtonGroup-bidi.css.patch | 15 +
...odules__CdxToggleButtonGroup-rtl.css.patch | 15 +
...t__modules__CdxToggleButtonGroup.css.patch | 15 +
...__dist__modules__CdxTooltip-bidi.css.patch | 15 +
...x__dist__modules__CdxTooltip-rtl.css.patch | 15 +
...codex__dist__modules__CdxTooltip.css.patch | 15 +
scripts/apply-codex-patch.mjs | 111 +++
scripts/lib-codex-patch.mjs | 168 +++++
scripts/patch-codex.mjs | 420 +++++++----
.../example-codex-kitchen-sink/index.vue | 17 +-
95 files changed, 4093 insertions(+), 169 deletions(-)
create mode 100644 patches/codex/manifest.json
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-dark.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-large.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-small.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-x-large.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.css.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.js.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.less.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.scss.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-dark.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-large.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-small.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-x-large.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.css.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.js.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.json.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.less.patch
create mode 100644 patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.scss.patch
create mode 100644 patches/codex/wikimedia__codex__dist__codex.style-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__codex.style-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__codex.style.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxAccordion-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxAccordion-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxAccordion.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxButton-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxButton-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxButton.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxCard-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxCard-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxCard.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxCheckbox.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxChipInput-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxChipInput-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxChipInput.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxDialog-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxDialog-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxDialog.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxImage-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxImage-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxImage.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxMenu-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxMenu-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxMenu.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxMessage-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxMessage-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxMessage.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxPopover-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxPopover-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxPopover.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxProgressBar.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxSearchInput.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxSelect-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxSelect-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxSelect.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTable-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTable-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTable.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTabs-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTabs-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTabs.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTextArea-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTextArea-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTextArea.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTextInput-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTextInput-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTextInput.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxThumbnail.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxToggleButton.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTooltip-bidi.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTooltip-rtl.css.patch
create mode 100644 patches/codex/wikimedia__codex__dist__modules__CdxTooltip.css.patch
create mode 100644 scripts/apply-codex-patch.mjs
create mode 100644 scripts/lib-codex-patch.mjs
diff --git a/.agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md b/.agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md
index 39782e7..4aca8b5 100644
--- a/.agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md
+++ b/.agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md
@@ -1,34 +1,93 @@
# Testing a Codex Gerrit patch in ProtoWiki
-Use this when you want to trial an unmerged `design/codex` change in ProtoWiki without publishing a package.
+Use this when you want to trial an unmerged `design/codex` change in ProtoWiki
+— locally **and** on the deployed PR preview — without publishing a package.
-## Commands
+The approach commits a small, **deterministic diff** under `patches/codex/`
+(no vendored tarballs). All the heavy work (cloning + building Codex) happens on
+your machine; CI only re-formats the published Codex files and re-applies the
+committed diff at install time, with no Codex build step.
-Apply the current patchset for a change:
+The diff covers the change's **full distribution footprint** — every built file
+under `dist/**` plus the root `theme-*` token files that actually differ — not
+just the handful ProtoWiki imports today. That keeps the patched `node_modules`
+self-contained: any current or future import sees the change, and you never have
+to re-run just because you added a new import. Each individual file diff stays
+small (formatting normalizes the minified CSS), so even the complete footprint is
+a manageable set of text diffs rather than committed tarballs.
+
+## Make the patch
```bash
npm run patch-codex -- https://gerrit.wikimedia.org/r/c/design/codex/+/1288878
```
-The command:
-
-- reads Gerrit metadata for the given change
-- fetches the current patchset into a local cache checkout
-- builds `design/codex`
-- packs and installs local tarballs for all three packages:
- - `@wikimedia/codex`
- - `@wikimedia/codex-design-tokens`
- - `@wikimedia/codex-icons`
-
-Reset to normal registry-resolved packages:
+This is a "make + apply" command. It:
+
+1. Establishes a pristine baseline: deletes any existing `patches/codex/`,
+ removes the installed Codex packages, and runs `npm install` so
+ `node_modules` holds the real published Codex (the patch's baseline).
+2. Fetches the change: reads Gerrit metadata for the current patchset and
+ fetches the change commit **and its parent** into a local cache checkout.
+3. Builds twice (heavy, local): builds the Codex packages at the **parent**
+ commit (unpatched) and at the **change** commit (patched).
+4. Detects the footprint: the distributed files (`dist/**` + root `theme-*`)
+ that differ between the two builds are exactly what the change touches —
+ auto-detected, not a hardcoded list, so it scales to whatever files a change
+ touches. Package metadata (`package.json` / `README` / `LICENSE`) is excluded.
+5. Builds a tiny per-file diff anchored to the published artifact: it formats the
+ published file, the unpatched build, and the patched build with the committed
+ Prettier config, 3-way merges so only the unpatched→patched change is layered
+ onto the published file, then diffs `format(published)` → merged.
+6. Writes the small patches to `patches/codex/*.patch` plus `manifest.json`
+ (Codex versions, change id, Prettier version, file list).
+7. Applies the patch locally via `scripts/apply-codex-patch.mjs` (the same path
+ CI uses), so local == CI.
+
+Commit `patches/codex/`. Because the deploy / preview workflows run
+`npm ci && npm run build`, the `postinstall` applier reproduces the change in the
+PR preview.
+
+## How it is applied (and why it is small + deterministic)
+
+`scripts/apply-codex-patch.mjs` is wired as `postinstall`, so it runs on every
+`npm install` / `npm ci`. For each file in the manifest it:
+
+- re-formats the freshly installed (published) file with the committed Prettier
+ config — bypassing `.prettierignore`, since the targets live in `node_modules`;
+- applies the committed diff idempotently with `git apply` (it reverse-checks
+ first, so re-running is a no-op).
+
+- **Small per file**: formatting both sides normalizes the minified single-line
+ `codex.style.css` (one ~170 KB line), so each file's diff is proportional to the
+ real change, not the file size. Patching the whole distribution is therefore a
+ few thousand lines of text diff total, not megabytes of tarball.
+- **Deterministic**: the diff is always applied to the **fixed, integrity-locked
+ published artifact** after re-formatting with the **pinned** Prettier (same
+ version via the lockfile, same committed `.prettierrc.json`). Same inputs
+ everywhere → `git apply` always lands. If it ever can't apply cleanly, the
+ applier errors loudly instead of producing a wrong build.
+- **No CI build**: the applier is a fast text pass (format + apply), never a
+ Codex build.
+
+## Reset
```bash
npm run patch-codex:reset
```
-## Why all three packages are patched together
+Deletes `patches/codex/`, removes the installed Codex packages, and reinstalls
+the registry-resolved packages from the `package.json` ranges. Run this before
+merging if the upstream change is not yet released, so the branch does not ship a
+patched Codex to production.
+
+## Why all three packages are considered together
-ProtoWiki consumes all three Codex packages directly, and `@wikimedia/codex` depends on matching icon/token outputs. Keeping them in lockstep avoids mixed-version visual bugs.
+ProtoWiki consumes all three Codex packages directly
+(`@wikimedia/codex`, `@wikimedia/codex-design-tokens`, `@wikimedia/codex-icons`),
+and `@wikimedia/codex` depends on matching icon/token outputs. The footprint
+detection inspects all three, so a change touching any of them is captured in
+lockstep — avoiding mixed-version visual bugs.
## Suggested smoke-test routes
@@ -45,5 +104,16 @@ Focus on light/dark theme and desktop/mobile skin states.
## Notes
-- The cache checkout lives under your system temp directory (`$TMPDIR/protowiki-codex-gerrit-cache` on macOS).
-- This workflow uses `npm install --no-save`, so it does not intentionally rewrite dependency ranges in `package.json`.
+- The cache checkout lives under your system temp directory
+ (`$TMPDIR/protowiki-codex-gerrit-cache` on macOS) and is reused across runs.
+- **Regenerate on an upstream bump.** The patch is anchored to a specific
+ published Codex version and Prettier version. If you bump `@wikimedia/codex`
+ (see [`protowiki-update-codex`](../SKILL.md)) or Prettier, re-run
+ `npm run patch-codex -- ` to regenerate, then commit the refreshed
+ `patches/codex/`. (Same "regenerate on upstream bump" you'd have with
+ patch-package.)
+- If the Gerrit change gets a new patchset, just re-run `npm run patch-codex`
+ to refresh the diff.
+- Optimized for token/style/CSS-level changes (the common Codex-trial case). A
+ change that significantly alters the bundled JS is still captured, but its
+ formatted diff would be larger.
diff --git a/package-lock.json b/package-lock.json
index 4b9d607..2036a5e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,13 @@
{
"name": "protowiki",
- "version": "0.1.0",
+ "version": "0.2.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "protowiki",
- "version": "0.1.0",
+ "version": "0.2.0",
+ "hasInstallScript": true,
"dependencies": {
"@wikimedia/codex": "^2.6.0",
"@wikimedia/codex-design-tokens": "^2.6.0",
@@ -18,6 +19,7 @@
"@types/node": "^22.10.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-typescript": "^14.0.0",
+ "diff": "^9.0.0",
"eslint": "^9.16.0",
"eslint-plugin-vue": "^10.0.0",
"postcss": "^8.5.12",
@@ -2255,6 +2257,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/diff": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-9.0.0.tgz",
+ "integrity": "sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
diff --git a/package.json b/package.json
index c04335e..349e277 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"format": "prettier --write \"src/**/*.{ts,vue,css}\"",
"lint": "eslint . --ext .ts,.vue",
"snapshot:wiki-skins": "bash scripts/snapshot-wiki-skins.sh",
+ "postinstall": "node scripts/apply-codex-patch.mjs",
"patch-codex": "node scripts/patch-codex.mjs",
"patch-codex:reset": "node scripts/patch-codex.mjs --reset"
},
@@ -26,6 +27,7 @@
"@types/node": "^22.10.0",
"@vitejs/plugin-vue": "^6.0.0",
"@vue/eslint-config-typescript": "^14.0.0",
+ "diff": "^9.0.0",
"eslint": "^9.16.0",
"eslint-plugin-vue": "^10.0.0",
"postcss": "^8.5.12",
diff --git a/patches/codex/manifest.json b/patches/codex/manifest.json
new file mode 100644
index 0000000..2514d7c
--- /dev/null
+++ b/patches/codex/manifest.json
@@ -0,0 +1,710 @@
+{
+ "change": "1288878",
+ "patchset": 2,
+ "revision": "6f06d901bc3a78a290ca2de006bf94fee8a25284",
+ "generatedAt": "2026-06-17T11:01:18.975Z",
+ "codexVersions": {
+ "@wikimedia/codex": "2.6.0",
+ "@wikimedia/codex-design-tokens": "2.6.0",
+ "@wikimedia/codex-icons": "2.6.0"
+ },
+ "prettierVersion": "3.8.3",
+ "files": [
+ {
+ "target": "@wikimedia/codex/dist/codex.style-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/codex.style-bidi.css",
+ "patch": "wikimedia__codex__dist__codex.style-bidi.css.patch",
+ "baseSha": "8904c14fad35778e350a0d15d85597bce4f17100a2818e07351c35c0ac8e118c",
+ "patchedSha": "a6ee3905826c21299c920983bee2f5e5d1b36d139af46910cbdaa2ebb3a4ec8c"
+ },
+ {
+ "target": "@wikimedia/codex/dist/codex.style-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/codex.style-rtl.css",
+ "patch": "wikimedia__codex__dist__codex.style-rtl.css.patch",
+ "baseSha": "79ec4aca31509b82b146fd48c7c9edb70c0c7a13d78662e123517a03d85008c3",
+ "patchedSha": "d1750bf7d038455cafbf6514c204a31a4f6ba880f37c7fe26c24a1417678f809"
+ },
+ {
+ "target": "@wikimedia/codex/dist/codex.style.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/codex.style.css",
+ "patch": "wikimedia__codex__dist__codex.style.css.patch",
+ "baseSha": "9b76b0eb1717ee84ad42ba8d5c59085d981d444ebd7696d7bb2658de6206d909",
+ "patchedSha": "ea10b8218cc876b32767eb11238a85ceb957a3be0a44f3cc83e0d65ae9b89467"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxAccordion-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxAccordion-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxAccordion-bidi.css.patch",
+ "baseSha": "d803fd7339951206ac07aef0656e9e4c2c1d0b0cddcfe9ecd3b9283024f96e86",
+ "patchedSha": "b692b2ea44ff59fce6fdc6f6386de03fda3d60e5beb04a529267255b1554ea05"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxAccordion-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxAccordion-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxAccordion-rtl.css.patch",
+ "baseSha": "112c89114caade2afa33913c8fbc6950b5e6c390612c9ba82c5847ba960af7cd",
+ "patchedSha": "0cb3b13fa8d2d33d312bbfbef0982a7f8cfec5924d9bea5084c0d657f14e550b"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxAccordion.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxAccordion.css",
+ "patch": "wikimedia__codex__dist__modules__CdxAccordion.css.patch",
+ "baseSha": "30b133cea95bae082af4d7ec323e625e4e0a1a4cad0e6df842790ddb34a37654",
+ "patchedSha": "d23ef4085da8fb3f41bf839814d88404a5fdf60c619a6681c3f522f407ea583f"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxButton-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxButton-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxButton-bidi.css.patch",
+ "baseSha": "c4586344d5fb0dc6af11209259d7077b62ab2cb8acae0c87c8178e3c4a9e76ac",
+ "patchedSha": "f6916cf15f8dee0f27453fed602fedc7edd7b8ff6f8c84f810c2c7481a24b710"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxButton-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxButton-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxButton-rtl.css.patch",
+ "baseSha": "27d1b9ed6c7edffc48dfe855106653ca19fa5a026df77031b2f7c26c0a4e49a6",
+ "patchedSha": "91785c2890a11ecf427ab17e608f8dde37ad8ab60c0a8992bff03638d5071af0"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxButton.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxButton.css",
+ "patch": "wikimedia__codex__dist__modules__CdxButton.css.patch",
+ "baseSha": "09ae3dbe1e3a0ab0aeb0a0e73df3e5a95c31b1d39225b7ef3feeac7caa0f5ba3",
+ "patchedSha": "f63b3e2c9bd3c6a0455a07b8bfbb441ccd5c4a52fe59643a227bdb0686dc18f8"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxButtonGroup-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxButtonGroup-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxButtonGroup-bidi.css.patch",
+ "baseSha": "8feea7a6e06f819f1fb9f99d894cd223713738e7c1194807905a3745882d42ed",
+ "patchedSha": "c55df98c87cabacdd1b8e629e2262d5161b0744f3bd8c8578f97df5aa4231f5c"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxButtonGroup-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxButtonGroup-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxButtonGroup-rtl.css.patch",
+ "baseSha": "e12301260e13fe99de0d651b2099b8d8d3bd3ed559a1feb02321feadcbad0662",
+ "patchedSha": "a995753ddf8c082d16211bd19f600a61832164c43c4f95880073a46574eeb02f"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxButtonGroup.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxButtonGroup.css",
+ "patch": "wikimedia__codex__dist__modules__CdxButtonGroup.css.patch",
+ "baseSha": "289e7df75ec354536466e35b55a9f7eccf9928e4ad761fe98a8b23441d4a1c45",
+ "patchedSha": "4c47c397a6ff3d9de2105f99f851d7fa70ee949ba126338b27904f607ee367d9"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxCard-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxCard-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxCard-bidi.css.patch",
+ "baseSha": "965a3c64ec316d43558e03e66c5eafb61f46d7a03da40ed72d3854cd8ae7e626",
+ "patchedSha": "e9cb948869dd6fdd046db3afcf601f4f54d7c8715e3cc39f267e94a749d39aa1"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxCard-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxCard-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxCard-rtl.css.patch",
+ "baseSha": "0e8bb06039549db0353ba6ce383e660c3be5ff37adb1fd899d53c6f6eb08d0f5",
+ "patchedSha": "2670e28f7f5aad045d66cfb395c09c015c9d6037644bc67a2dcceeca707b68bd"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxCard.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxCard.css",
+ "patch": "wikimedia__codex__dist__modules__CdxCard.css.patch",
+ "baseSha": "87c2bcdb322f4c7a869c7bded656f130feea37f8117d2d8bf1153a6a1d79bd5e",
+ "patchedSha": "2c79ca5e60f6b5539a33a10309b603b9b918795a03173bc5ffaa3354bda8c3e8"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxCheckbox-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxCheckbox-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxCheckbox-bidi.css.patch",
+ "baseSha": "68fd14666239c7f77872f09b474863f140a9265967e5ee3e3f0cb79309823f4a",
+ "patchedSha": "fb1ff37d743e57ae5c890d998796e82cd682e3c263ff14a9e239d5bdb0cb36c2"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxCheckbox-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxCheckbox-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxCheckbox-rtl.css.patch",
+ "baseSha": "5ab6e1effee90b0dbb1380eff524d7061c6c71e4afff3356a74734ef3835c017",
+ "patchedSha": "243ef6785d34576143d8954b1df12e255a3867346fa5b7a2b95d922861bff7fd"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxCheckbox.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxCheckbox.css",
+ "patch": "wikimedia__codex__dist__modules__CdxCheckbox.css.patch",
+ "baseSha": "8bbe0b3fe85ebf91b4bcb06dda962e183d1022ef1399c07e16e6887994fc69b7",
+ "patchedSha": "a2f00db60dc9419fd9705b75685499ec23755bdfd042d38c82893aa5e1783e51"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxChipInput-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxChipInput-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxChipInput-bidi.css.patch",
+ "baseSha": "fc66cd9d85356b0b72903ce529e12e5a261956a303f6a966332c3e75ffc05504",
+ "patchedSha": "ad3434a974837ea4bfc8b808d2899995c270a867dbb1f0da25524dbe6e6332f2"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxChipInput-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxChipInput-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxChipInput-rtl.css.patch",
+ "baseSha": "505ec39ed353514d3957ec7245db6c316a4503bc9cee76ad6350472da84524fd",
+ "patchedSha": "0b0bc9ba8d1e64b9da85a2c2a06f26656dbec6dd076afc85e987b90b8b6f196f"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxChipInput.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxChipInput.css",
+ "patch": "wikimedia__codex__dist__modules__CdxChipInput.css.patch",
+ "baseSha": "c88f2c7cb97665f3a3d5c2943a07b3420baaa147587f57e5992624e24bc230e8",
+ "patchedSha": "889135cd8d7e49c4a2a5448b3cdb79d24ed6c60e3712212ae81c7923cc8a86e7"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxDialog-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxDialog-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxDialog-bidi.css.patch",
+ "baseSha": "c1c83648100a85a8751a21d8a7b2afaa5a9c3411b74407297fadfb3d886f310e",
+ "patchedSha": "79897286c47e9c624230491fe7fb89f99ceda876cc45361a3d5621b9d6df3b30"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxDialog-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxDialog-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxDialog-rtl.css.patch",
+ "baseSha": "478d3560e13c421a757e48232ec44a5fad58646ab0ece355ab2fe4492e2d78fd",
+ "patchedSha": "128ec02cab543898b2a77f4b41a28da6729fe9aaff9bc6abac82b19514531017"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxDialog.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxDialog.css",
+ "patch": "wikimedia__codex__dist__modules__CdxDialog.css.patch",
+ "baseSha": "aba137e09987f760ab633f44bd5fe2c8c099e2689acf708fddfc11e8995af9df",
+ "patchedSha": "cc6aec4a9b3c38f3e18ea83ebb199cc0336ae6efccb7b0d04c5e9324a8d649fe"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxImage-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxImage-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxImage-bidi.css.patch",
+ "baseSha": "b305aa18db29ad7260f186d4fa9add23ecb02b508ff9b5d156fb1030d74aa6b7",
+ "patchedSha": "2fc73fbd15520ef62517faefa692533bf71d4c4b6b432105f3d941633e1457e0"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxImage-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxImage-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxImage-rtl.css.patch",
+ "baseSha": "ce92123f06d7c971379d8110f82679c7a35d96d41568ac1e908a39c726fff6b6",
+ "patchedSha": "8237e3fe3aff9523d6ec5336f91a19bd9fdb8e09b18fd0d3d09eb21aa722f210"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxImage.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxImage.css",
+ "patch": "wikimedia__codex__dist__modules__CdxImage.css.patch",
+ "baseSha": "a9dfd04dc2ce2172a53bee6a6f210e8c0b84f9178ee7f55a55412405ba512114",
+ "patchedSha": "6c6a669d3ed078a5895708ef0c5b1a5315e3da56cae0360120fa5e54d6b6e340"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxMenu-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxMenu-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxMenu-bidi.css.patch",
+ "baseSha": "af87b08e3cbfea183ff06916fc093d6549b60b603395b9e4b7717d87546a5af4",
+ "patchedSha": "80681025def2c3df09229193a5278a06353ed0f7346df899cc00a6ebd54e2382"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxMenu-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxMenu-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxMenu-rtl.css.patch",
+ "baseSha": "0cae371be5132921d2f87bc42e19d260f4e784d9cbc7629a9f109126ed320c0f",
+ "patchedSha": "a7d789dee0400e9427dfe3f0c5a973db623d43d8d66809320aa7571a9b009206"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxMenu.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxMenu.css",
+ "patch": "wikimedia__codex__dist__modules__CdxMenu.css.patch",
+ "baseSha": "d59543545b0c214ec6facb1f65d46d2f330fd064fc64372b68025a2592559a26",
+ "patchedSha": "c1d9edf6e12edf39c8de644d67356aad77760260082555ee844215a9426295f0"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxMessage-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxMessage-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxMessage-bidi.css.patch",
+ "baseSha": "8e3ef1f66a92352944b06af85adcc04e8715bf3966dccffeb9d6f4cfa7e6faf2",
+ "patchedSha": "2300cc98f3f33e01f97daae1def11bf22c55dc2b9e72a43871354ef4e132b5d0"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxMessage-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxMessage-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxMessage-rtl.css.patch",
+ "baseSha": "81caa67c6d35184f98ee66282e31696a2ad2bc4bcb4765430c2c16f01ba5a46f",
+ "patchedSha": "526553d5f9a0c108f624ecabfcb3eda1876055147e8fe761bb5cdcfb5d5393fc"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxMessage.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxMessage.css",
+ "patch": "wikimedia__codex__dist__modules__CdxMessage.css.patch",
+ "baseSha": "ce40adc2107664b3489202db6398bb1e4238dace33b05b0a3e966a9c3684965d",
+ "patchedSha": "20b2d3b01ee34afce1cd4b2609316c44759182f33b69464a9d12b313ac836be4"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxPopover-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxPopover-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxPopover-bidi.css.patch",
+ "baseSha": "d6ff586ca0b024c61b976329e0537e185a2101bbd6e2e8e1d1fd77878d77e7db",
+ "patchedSha": "b6ca9b7e8dad4e0f48f7e558a52c15cc682f5ee0e9830a3d91dd0c5f0ecd3ed1"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxPopover-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxPopover-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxPopover-rtl.css.patch",
+ "baseSha": "931a470dfd535d2aca03383334979464c3a1b5f3f7d13d2e151583aca0eff532",
+ "patchedSha": "bc025854b0e900df24128d9057b38cf7a8613b136002eb0a1ba1706d8f48ab04"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxPopover.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxPopover.css",
+ "patch": "wikimedia__codex__dist__modules__CdxPopover.css.patch",
+ "baseSha": "b769a9353e48765337bbd19c62ba87735eb9fd87296c3ac3c05e5affa35b9b76",
+ "patchedSha": "f321ec5418dedc44ba81754fb5d84e611d6c92f331536278ce153e9f0ff7af98"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxProgressBar-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxProgressBar-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxProgressBar-bidi.css.patch",
+ "baseSha": "7ff2afa9a25af0f834b6f247a2303b4a1457175617ffcb278a467bc3f678eb15",
+ "patchedSha": "efdb0d8a14fd3b8f6ec1d84976d09add9739489c0460af48c744bb270212c1a8"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxProgressBar-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxProgressBar-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxProgressBar-rtl.css.patch",
+ "baseSha": "f5cbb33d0b0f2bf7d3eb6b9b604684225c4c2559dec4cac413630059f5294734",
+ "patchedSha": "52d97290b898b2d4164e55b6562dcd8ec407f6b89023549359d7fc6a85a86653"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxProgressBar.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxProgressBar.css",
+ "patch": "wikimedia__codex__dist__modules__CdxProgressBar.css.patch",
+ "baseSha": "bf665f0509623a33693cbcdce704ba606b72a04a2a76da4848aff94424cd54a8",
+ "patchedSha": "1e437ae4120a8c9a4dea4fd82c883bf0f15339a9976aebe2598d462c84f0a155"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxSearchInput-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxSearchInput-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxSearchInput-bidi.css.patch",
+ "baseSha": "1101ea58a0cbc68e8e316210275a6e62fc9a88246c4fc4c57b99448c970a2c2a",
+ "patchedSha": "a9a3c48c89928426b527da77d26d68e7f6a46c2599b5c46c88deb4807c2ac720"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxSearchInput-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxSearchInput-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxSearchInput-rtl.css.patch",
+ "baseSha": "29800680e2760590f5ad4c5fa0f747395796259312aabdb2f066fb251b61ea9b",
+ "patchedSha": "7e0128e2c1d75e166ce8482421c8884ab4e2ec18798193fbe7216624188c6871"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxSearchInput.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxSearchInput.css",
+ "patch": "wikimedia__codex__dist__modules__CdxSearchInput.css.patch",
+ "baseSha": "bddeefd7c8a077ff4cb5ffdf15be7b6bc91faee080c33ce7079d26d008a3ecd9",
+ "patchedSha": "96b3af99e035ab595d5d6d31531d73c99dcc2d6fcd0d560a6f996aadf86a5eaa"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxSelect-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxSelect-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxSelect-bidi.css.patch",
+ "baseSha": "5ef7c1f5dd7164f784d8d4baddaf674ef5daa32a60e07d3fdf4acfc3463068be",
+ "patchedSha": "fd95453c36b902e55d8d584e09f30bb5bea74f71b6393c919f45dd3fe2ddc649"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxSelect-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxSelect-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxSelect-rtl.css.patch",
+ "baseSha": "c3ce6af867b9db63d81d0108fb421a173e0136bb9918882be8c79744651f284b",
+ "patchedSha": "4801c7d542f0dcd58312830251edc29afa79a61399dd16989034ecd3dec1eb8a"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxSelect.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxSelect.css",
+ "patch": "wikimedia__codex__dist__modules__CdxSelect.css.patch",
+ "baseSha": "e52f464ce38827bc8593e3b5cbf136c75bcce82e27f58bf1949e317c97a01504",
+ "patchedSha": "87146ef06b46789ad9008991dae54ab522cd32cb56fdbe34d1d1848033fa9fcd"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTable-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTable-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTable-bidi.css.patch",
+ "baseSha": "f91150afe86c1b6fa15dfc9c3c49f8da4440fe7149bb2385b98c82df2b2feb59",
+ "patchedSha": "68c03f82031e6d0243d987570eff84b67185c0eab887f68b97853f9fe2873d72"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTable-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTable-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTable-rtl.css.patch",
+ "baseSha": "c9b8d575935a958460a5d68250e1fbe1776c10745f1c85b5fe8cdbac1f328b23",
+ "patchedSha": "4d57907008eaca02ff34fc9ad0f3b40363cc6bf797a1936b698351ea7081a44b"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTable.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTable.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTable.css.patch",
+ "baseSha": "4824b8b63c2beee8f636cfa801cfb3349cde8c13921bead839fed066ad6ca580",
+ "patchedSha": "fff3344f0607401791c1660575813fd4f57ef250f7418e8e3b4f426bc72f1c9c"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTabs-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTabs-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTabs-bidi.css.patch",
+ "baseSha": "cf199b3741285ad9a3883b4ff3b95b7eee2f0ced512fcad1fbdd6235ea10d799",
+ "patchedSha": "d6f4eda4e014554ce55e57fb042b362e01cc30db5bdf8a680d8ce6cf67e2a769"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTabs-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTabs-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTabs-rtl.css.patch",
+ "baseSha": "194c226006779a8478b4bd7ce3a2c9e4b30bcfd222b29124390cd9d1b582fe6e",
+ "patchedSha": "2223a7de6a02c4a03df652e9dd4922c2ae16e20c7f0632a518621f0394c6a3df"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTabs.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTabs.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTabs.css.patch",
+ "baseSha": "68b2599b4f116dd77601ff720d9d3538dca3b6f227f4d05bf26fe675a3063b57",
+ "patchedSha": "e8e26c0bac65bbe34490507c328851717e3c3cc543fb3b94285d8356b32b48e5"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTextArea-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTextArea-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTextArea-bidi.css.patch",
+ "baseSha": "0227caf55625be4c072555915d0b4633c297e451fa6cc302959bda91e93ab0bd",
+ "patchedSha": "1c30051451ca70d0cd20096ceab0f17c2d38a907fb699eecb74526660dfecef6"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTextArea-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTextArea-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTextArea-rtl.css.patch",
+ "baseSha": "875ede201929d2fe60d4992facd90b39db7ab700aff1a6d52661ba2acc212746",
+ "patchedSha": "83528a70f7be5a658791ac01bc69af1f725e895560ce2277b63defb954cc1778"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTextArea.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTextArea.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTextArea.css.patch",
+ "baseSha": "c1cc8a438bc49e405732f954e01af8b5fdb67ef1d43a650829ee06460bcbc290",
+ "patchedSha": "58d9e27c96525cc45998a975ec23cb53d078e059aea360b17e948c18709811ee"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTextInput-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTextInput-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTextInput-bidi.css.patch",
+ "baseSha": "08d2b44c633ac4d52c0ca210b8787ba2498167698d9da7720d05c53187c1450c",
+ "patchedSha": "752ca4ebd1e2908a83e61d3f72bb514b1ef725f7e1424d18fc69aa5470f84dcb"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTextInput-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTextInput-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTextInput-rtl.css.patch",
+ "baseSha": "829d4506f5e22b31900cd70c713646cbffa7abd15216c7437379b6f4cc981593",
+ "patchedSha": "86c85fdd55446d375722fedbf66112b6e50f2375ce5a088d7ee99e9c70d8accd"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTextInput.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTextInput.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTextInput.css.patch",
+ "baseSha": "287cfefa9b1cc096d1eb3bf5395645e80f4983058a4a716147185e0c482ccfd5",
+ "patchedSha": "0db9d5c803d38009159d09f1ec56b814a999efe6d3503b10f6c1f5cf28d8edf9"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxThumbnail-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxThumbnail-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxThumbnail-bidi.css.patch",
+ "baseSha": "e7a6d65500857c68d7de0599a8a573c9d01c25116fbbea0f34bc46b7c25784c4",
+ "patchedSha": "f2a2551ecc36fd78f8ebeaae6e0d657fff2713f18ae71d4098ebaa613252d549"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxThumbnail-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxThumbnail-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxThumbnail-rtl.css.patch",
+ "baseSha": "63a1adba4f028d20ca18b5ff0db6ec9e459afdc878ec4e43ae03a7b9c9c639a0",
+ "patchedSha": "89f221e6edb89c44f3d3769c97209d9bfbf40d6ed3cdb886fa27953aaee67482"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxThumbnail.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxThumbnail.css",
+ "patch": "wikimedia__codex__dist__modules__CdxThumbnail.css.patch",
+ "baseSha": "63a1adba4f028d20ca18b5ff0db6ec9e459afdc878ec4e43ae03a7b9c9c639a0",
+ "patchedSha": "89f221e6edb89c44f3d3769c97209d9bfbf40d6ed3cdb886fa27953aaee67482"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxToggleButton-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxToggleButton-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxToggleButton-bidi.css.patch",
+ "baseSha": "5825b15dbf7d93cd51e9d09f8d8def54f2317339d2205589b26f8c0f447696df",
+ "patchedSha": "6a2172ae42c31fe99a688005e792f20e4c613115ff8516e9628abab59ccf4aa6"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxToggleButton-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxToggleButton-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxToggleButton-rtl.css.patch",
+ "baseSha": "a90a8d51c63b0ba159c6c060c585d77b7ea84e42c9860bc635bd0ac5daf97f7e",
+ "patchedSha": "de5b13de82db6f24075a1d30cabfd55fe80515482272e615d4962dbd482962b6"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxToggleButton.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxToggleButton.css",
+ "patch": "wikimedia__codex__dist__modules__CdxToggleButton.css.patch",
+ "baseSha": "21b48aad5a71da18a481558d5534599290103247bcdf4d21535f5fbc04aee363",
+ "patchedSha": "684f0ddcf13f578bf586b0309867e5b8ce251225cd1d188561d1912241615ed7"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxToggleButtonGroup-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxToggleButtonGroup-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxToggleButtonGroup-bidi.css.patch",
+ "baseSha": "d6a94a53d5ef7c4a81bf9a252a6cba80ca81605482686b0bf48e9579a2d3ceb2",
+ "patchedSha": "19580ba09593ed8fe05ff186560049591bd9d7d2f2fa6e6a69f75c8a26792ab8"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxToggleButtonGroup-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxToggleButtonGroup-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxToggleButtonGroup-rtl.css.patch",
+ "baseSha": "47937ae7c36b3f1e7394642725c777e7a85d879145552fc6759affdcc71db2f8",
+ "patchedSha": "8d28fea9345813872bf497e1d223f0b322f7bc5112c10246ff2ba87b277ea738"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxToggleButtonGroup.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxToggleButtonGroup.css",
+ "patch": "wikimedia__codex__dist__modules__CdxToggleButtonGroup.css.patch",
+ "baseSha": "f7e4496a0453519b91c8f4f2851f52eaa1667963050fbf3139b56ce8337670aa",
+ "patchedSha": "4c064d16b6ceb48110640dc2a63a0942ad51a4a735524b0a94fc13ffd4555709"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTooltip-bidi.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTooltip-bidi.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTooltip-bidi.css.patch",
+ "baseSha": "de977b77b0ad6d48d15629290cd83e4fc0de9477644e5da00a1cf7836054d053",
+ "patchedSha": "ee8197021435b685ed28902ad7d38d31c8626ce5b290ff085cb2718ae02d1d87"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTooltip-rtl.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTooltip-rtl.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTooltip-rtl.css.patch",
+ "baseSha": "e2591b25b50d06ec43a894435b571599536052025b8dc648e2f78b2763d75594",
+ "patchedSha": "cf623f0a170b3d3b14891ef878e84e7ad4c1d71d612660018ae6b3feaba02438"
+ },
+ {
+ "target": "@wikimedia/codex/dist/modules/CdxTooltip.css",
+ "package": "@wikimedia/codex",
+ "rel": "dist/modules/CdxTooltip.css",
+ "patch": "wikimedia__codex__dist__modules__CdxTooltip.css.patch",
+ "baseSha": "e2591b25b50d06ec43a894435b571599536052025b8dc648e2f78b2763d75594",
+ "patchedSha": "cf623f0a170b3d3b14891ef878e84e7ad4c1d71d612660018ae6b3feaba02438"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui-mode-dark.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui-mode-dark.json",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-dark.json.patch",
+ "baseSha": "433af0fba1096d524ef87c05d3e5a5a85b7ce275a6321549e8ab95880126b6e0",
+ "patchedSha": "4eda3ced8dc30d8b8ada10a1c5bfe06339e5b6054b50dfc76db9dcbf9f6b4ba0"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui-mode-large.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui-mode-large.json",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-large.json.patch",
+ "baseSha": "c5119834056aae0532a4f3661a54fdd551fd777093823a5155c9ea822aa72e1a",
+ "patchedSha": "344af5a345c3699db2109fc4eb54cc9cb0cbbe5d9b1a78b6bdc0a768464d6c80"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui-mode-small.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui-mode-small.json",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-small.json.patch",
+ "baseSha": "770cc1cb3654f04cd8d1fc961d95098b397969d3fae0a2355b35bec9b349b202",
+ "patchedSha": "f3c958c6929547c1fa1ebf71ccc023d8241e2c533856e81ee1fa010bdab647bb"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui-mode-x-large.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui-mode-x-large.json",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-x-large.json.patch",
+ "baseSha": "41dd7e62a7b09faee3c5fd2d06b989b11d4d2acb3dfe01b9b82951fe6169bca0",
+ "patchedSha": "7eae6940b1f0dd076cdbb81480522e0ac0bd90fb0e42403825d13b10cdd8ac54"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui.css",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui.css",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.css.patch",
+ "baseSha": "3f5934a1b290ae0408b4ea9c76e42b5d99e200c7f1bf7191024a4b8c73c7cbb1",
+ "patchedSha": "18694255e2d953c118841e0554ec49537be8e2fd9f20da7fe3177f64bf859de4"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui.js",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui.js",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.js.patch",
+ "baseSha": "1a7e259d196ac0f79ee0690d78555828168c682fbc5ae595c9ecf03ae7083301",
+ "patchedSha": "6a84c6debbe3e8a2f98977cf31a4ee9822de9536d5cb4be2868076e215027316"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui.json",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.json.patch",
+ "baseSha": "a34bfa04a6e9321dc88127755dc0cd47f8b33ad43a7090844506e214b5e483b4",
+ "patchedSha": "59ebeb7436370466c0623f2d52a7eb4bd02b91d38b71ce990d3b15ba0a510d4c"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui.less",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui.less",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.less.patch",
+ "baseSha": "b641af174c3f8fa2b99ded6a2444d93a60c4cdd8f06c3471e178c79750146e47",
+ "patchedSha": "f29d1c31863bdb223874333770a6565940c913f880a0d380b2c5cc840abbc044"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/dist/theme-wikimedia-ui.scss",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "dist/theme-wikimedia-ui.scss",
+ "patch": "wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.scss.patch",
+ "baseSha": "4f0b75be3bb18ddda692f80cdb3992726f59a14a3d8e072cae7560c2bdad4bd0",
+ "patchedSha": "0e7af13b75cd9728bba9a8d5ba17217fa6ada92837491c4bf7000a8fde4795c5"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui-mode-dark.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui-mode-dark.json",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-dark.json.patch",
+ "baseSha": "433af0fba1096d524ef87c05d3e5a5a85b7ce275a6321549e8ab95880126b6e0",
+ "patchedSha": "4eda3ced8dc30d8b8ada10a1c5bfe06339e5b6054b50dfc76db9dcbf9f6b4ba0"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui-mode-large.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui-mode-large.json",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-large.json.patch",
+ "baseSha": "c5119834056aae0532a4f3661a54fdd551fd777093823a5155c9ea822aa72e1a",
+ "patchedSha": "344af5a345c3699db2109fc4eb54cc9cb0cbbe5d9b1a78b6bdc0a768464d6c80"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui-mode-small.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui-mode-small.json",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-small.json.patch",
+ "baseSha": "770cc1cb3654f04cd8d1fc961d95098b397969d3fae0a2355b35bec9b349b202",
+ "patchedSha": "f3c958c6929547c1fa1ebf71ccc023d8241e2c533856e81ee1fa010bdab647bb"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui-mode-x-large.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui-mode-x-large.json",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-x-large.json.patch",
+ "baseSha": "41dd7e62a7b09faee3c5fd2d06b989b11d4d2acb3dfe01b9b82951fe6169bca0",
+ "patchedSha": "7eae6940b1f0dd076cdbb81480522e0ac0bd90fb0e42403825d13b10cdd8ac54"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui.css",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui.css",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui.css.patch",
+ "baseSha": "3f5934a1b290ae0408b4ea9c76e42b5d99e200c7f1bf7191024a4b8c73c7cbb1",
+ "patchedSha": "18694255e2d953c118841e0554ec49537be8e2fd9f20da7fe3177f64bf859de4"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui.js",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui.js",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui.js.patch",
+ "baseSha": "1a7e259d196ac0f79ee0690d78555828168c682fbc5ae595c9ecf03ae7083301",
+ "patchedSha": "6a84c6debbe3e8a2f98977cf31a4ee9822de9536d5cb4be2868076e215027316"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui.json",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui.json",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui.json.patch",
+ "baseSha": "a34bfa04a6e9321dc88127755dc0cd47f8b33ad43a7090844506e214b5e483b4",
+ "patchedSha": "59ebeb7436370466c0623f2d52a7eb4bd02b91d38b71ce990d3b15ba0a510d4c"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui.less",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui.less",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui.less.patch",
+ "baseSha": "b641af174c3f8fa2b99ded6a2444d93a60c4cdd8f06c3471e178c79750146e47",
+ "patchedSha": "f29d1c31863bdb223874333770a6565940c913f880a0d380b2c5cc840abbc044"
+ },
+ {
+ "target": "@wikimedia/codex-design-tokens/theme-wikimedia-ui.scss",
+ "package": "@wikimedia/codex-design-tokens",
+ "rel": "theme-wikimedia-ui.scss",
+ "patch": "wikimedia__codex-design-tokens__theme-wikimedia-ui.scss.patch",
+ "baseSha": "4f0b75be3bb18ddda692f80cdb3992726f59a14a3d8e072cae7560c2bdad4bd0",
+ "patchedSha": "0e7af13b75cd9728bba9a8d5ba17217fa6ada92837491c4bf7000a8fde4795c5"
+ }
+ ]
+}
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-dark.json.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-dark.json.patch
new file mode 100644
index 0000000..662751d
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-dark.json.patch
@@ -0,0 +1,48 @@
+Index: dist/theme-wikimedia-ui-mode-dark.json
+===================================================================
+--- dist/theme-wikimedia-ui-mode-dark.json
++++ dist/theme-wikimedia-ui-mode-dark.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-large.json.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-large.json.patch
new file mode 100644
index 0000000..2f0a8c9
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-large.json.patch
@@ -0,0 +1,48 @@
+Index: dist/theme-wikimedia-ui-mode-large.json
+===================================================================
+--- dist/theme-wikimedia-ui-mode-large.json
++++ dist/theme-wikimedia-ui-mode-large.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-small.json.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-small.json.patch
new file mode 100644
index 0000000..f0fd6d8
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-small.json.patch
@@ -0,0 +1,48 @@
+Index: dist/theme-wikimedia-ui-mode-small.json
+===================================================================
+--- dist/theme-wikimedia-ui-mode-small.json
++++ dist/theme-wikimedia-ui-mode-small.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-x-large.json.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-x-large.json.patch
new file mode 100644
index 0000000..52aa884
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui-mode-x-large.json.patch
@@ -0,0 +1,48 @@
+Index: dist/theme-wikimedia-ui-mode-x-large.json
+===================================================================
+--- dist/theme-wikimedia-ui-mode-x-large.json
++++ dist/theme-wikimedia-ui-mode-x-large.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.css.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.css.patch
new file mode 100644
index 0000000..96e8bbf
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.css.patch
@@ -0,0 +1,15 @@
+Index: dist/theme-wikimedia-ui.css
+===================================================================
+--- dist/theme-wikimedia-ui.css
++++ dist/theme-wikimedia-ui.css
+@@ -364,9 +364,9 @@
+ --border-color-option-pink: #b5739e;
+ --border-color-option-maroon: #b57775;
+ --border-color-transparent: transparent;
+ --border-color-divider: #a2a9b1;
+- --border-radius-base: 2px;
++ --border-radius-base: 4px;
+ --border-radius-sharp: 0;
+ --border-radius-pill: 9999px;
+ --border-radius-circle: 50%; /* Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500 */
+ --outline-color-progressive--focus: #36c; /* Use in places where no more customized focus styles are provided, for example on generic `:focus`. */
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.js.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.js.patch
new file mode 100644
index 0000000..2d76e14
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.js.patch
@@ -0,0 +1,15 @@
+Index: dist/theme-wikimedia-ui.js
+===================================================================
+--- dist/theme-wikimedia-ui.js
++++ dist/theme-wikimedia-ui.js
+@@ -398,9 +398,9 @@
+ export const cdxBorderSubtle = '1px solid #c8ccd1'
+ export const cdxBorderProgressive = '1px solid #36c'
+ export const cdxBorderDestructive = '1px solid #f54739'
+ export const cdxBorderTransparent = '1px solid transparent'
+-export const cdxBorderRadiusBase = '2px'
++export const cdxBorderRadiusBase = '4px'
+ export const cdxBorderRadiusSharp = '0'
+ export const cdxBorderRadiusPill = '9999px'
+ export const cdxBorderRadiusCircle = '50%' // Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500
+ export const cdxOutlineBaseFocus = '1px solid transparent' // Enable Windows high contrast mode on certain widgets, that have default outlines overridden.
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.json.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.json.patch
new file mode 100644
index 0000000..6e44b61
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.json.patch
@@ -0,0 +1,48 @@
+Index: dist/theme-wikimedia-ui.json
+===================================================================
+--- dist/theme-wikimedia-ui.json
++++ dist/theme-wikimedia-ui.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.less.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.less.patch
new file mode 100644
index 0000000..ce0d6fc
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.less.patch
@@ -0,0 +1,15 @@
+Index: dist/theme-wikimedia-ui.less
+===================================================================
+--- dist/theme-wikimedia-ui.less
++++ dist/theme-wikimedia-ui.less
+@@ -426,9 +426,9 @@
+ @border-color-option-pink: var(--border-color-option-pink, #b5739e);
+ @border-color-option-maroon: var(--border-color-option-maroon, #b57775);
+ @border-color-transparent: var(--border-color-transparent, transparent);
+ @border-color-divider: var(--border-color-divider, #a2a9b1);
+-@border-radius-base: 2px;
++@border-radius-base: 4px;
+ @border-radius-sharp: 0;
+ @border-radius-pill: 9999px;
+ @border-radius-circle: 50%; // Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500
+ @outline-color-progressive--focus: var(
diff --git a/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.scss.patch b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.scss.patch
new file mode 100644
index 0000000..1cdbeeb
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__dist__theme-wikimedia-ui.scss.patch
@@ -0,0 +1,15 @@
+Index: dist/theme-wikimedia-ui.scss
+===================================================================
+--- dist/theme-wikimedia-ui.scss
++++ dist/theme-wikimedia-ui.scss
+@@ -361,9 +361,9 @@
+ $border-color-option-pink: #b5739e;
+ $border-color-option-maroon: #b57775;
+ $border-color-transparent: transparent;
+ $border-color-divider: #a2a9b1;
+-$border-radius-base: 2px;
++$border-radius-base: 4px;
+ $border-radius-sharp: 0;
+ $border-radius-pill: 9999px;
+ $border-radius-circle: 50%; // Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500
+ $outline-color-progressive--focus: #36c; // Use in places where no more customized focus styles are provided, for example on generic `:focus`.
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-dark.json.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-dark.json.patch
new file mode 100644
index 0000000..8d35564
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-dark.json.patch
@@ -0,0 +1,48 @@
+Index: theme-wikimedia-ui-mode-dark.json
+===================================================================
+--- theme-wikimedia-ui-mode-dark.json
++++ theme-wikimedia-ui-mode-dark.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-large.json.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-large.json.patch
new file mode 100644
index 0000000..d514309
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-large.json.patch
@@ -0,0 +1,48 @@
+Index: theme-wikimedia-ui-mode-large.json
+===================================================================
+--- theme-wikimedia-ui-mode-large.json
++++ theme-wikimedia-ui-mode-large.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-small.json.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-small.json.patch
new file mode 100644
index 0000000..9dde599
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-small.json.patch
@@ -0,0 +1,48 @@
+Index: theme-wikimedia-ui-mode-small.json
+===================================================================
+--- theme-wikimedia-ui-mode-small.json
++++ theme-wikimedia-ui-mode-small.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-x-large.json.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-x-large.json.patch
new file mode 100644
index 0000000..7fdafbe
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui-mode-x-large.json.patch
@@ -0,0 +1,48 @@
+Index: theme-wikimedia-ui-mode-x-large.json
+===================================================================
+--- theme-wikimedia-ui-mode-x-large.json
++++ theme-wikimedia-ui-mode-x-large.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.css.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.css.patch
new file mode 100644
index 0000000..6dfeb67
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.css.patch
@@ -0,0 +1,15 @@
+Index: theme-wikimedia-ui.css
+===================================================================
+--- theme-wikimedia-ui.css
++++ theme-wikimedia-ui.css
+@@ -364,9 +364,9 @@
+ --border-color-option-pink: #b5739e;
+ --border-color-option-maroon: #b57775;
+ --border-color-transparent: transparent;
+ --border-color-divider: #a2a9b1;
+- --border-radius-base: 2px;
++ --border-radius-base: 4px;
+ --border-radius-sharp: 0;
+ --border-radius-pill: 9999px;
+ --border-radius-circle: 50%; /* Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500 */
+ --outline-color-progressive--focus: #36c; /* Use in places where no more customized focus styles are provided, for example on generic `:focus`. */
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.js.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.js.patch
new file mode 100644
index 0000000..8ff4466
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.js.patch
@@ -0,0 +1,15 @@
+Index: theme-wikimedia-ui.js
+===================================================================
+--- theme-wikimedia-ui.js
++++ theme-wikimedia-ui.js
+@@ -398,9 +398,9 @@
+ export const cdxBorderSubtle = '1px solid #c8ccd1'
+ export const cdxBorderProgressive = '1px solid #36c'
+ export const cdxBorderDestructive = '1px solid #f54739'
+ export const cdxBorderTransparent = '1px solid transparent'
+-export const cdxBorderRadiusBase = '2px'
++export const cdxBorderRadiusBase = '4px'
+ export const cdxBorderRadiusSharp = '0'
+ export const cdxBorderRadiusPill = '9999px'
+ export const cdxBorderRadiusCircle = '50%' // Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500
+ export const cdxOutlineBaseFocus = '1px solid transparent' // Enable Windows high contrast mode on certain widgets, that have default outlines overridden.
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.json.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.json.patch
new file mode 100644
index 0000000..f9cc227
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.json.patch
@@ -0,0 +1,48 @@
+Index: theme-wikimedia-ui.json
+===================================================================
+--- theme-wikimedia-ui.json
++++ theme-wikimedia-ui.json
+@@ -3991,8 +3991,22 @@
+ "type": "theme"
+ },
+ "path": ["dimension", "absolute", "2"]
+ },
++ "4": {
++ "value": "4px",
++ "filePath": "src/themes/wikimedia-ui.json",
++ "isSource": false,
++ "original": {
++ "value": "4px"
++ },
++ "name": "dimension-absolute-4",
++ "attributes": {
++ "tokens": [],
++ "type": "theme"
++ },
++ "path": ["dimension", "absolute", "4"]
++ },
+ "6": {
+ "value": "6px",
+ "filePath": "src/themes/wikimedia-ui.json",
+ "isSource": false,
+@@ -10699,17 +10713,17 @@
+ }
+ },
+ "border-radius": {
+ "base": {
+- "value": "2px",
++ "value": "4px",
+ "filePath": "src/application.json",
+ "isSource": false,
+ "original": {
+- "value": "{ dimension.absolute.2 }"
++ "value": "{ dimension.absolute.4 }"
+ },
+ "name": "border-radius-base",
+ "attributes": {
+- "tokens": ["dimension.absolute.2"],
++ "tokens": ["dimension.absolute.4"],
+ "type": "base"
+ },
+ "path": ["border-radius", "base"]
+ },
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.less.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.less.patch
new file mode 100644
index 0000000..34302d0
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.less.patch
@@ -0,0 +1,15 @@
+Index: theme-wikimedia-ui.less
+===================================================================
+--- theme-wikimedia-ui.less
++++ theme-wikimedia-ui.less
+@@ -426,9 +426,9 @@
+ @border-color-option-pink: var(--border-color-option-pink, #b5739e);
+ @border-color-option-maroon: var(--border-color-option-maroon, #b57775);
+ @border-color-transparent: var(--border-color-transparent, transparent);
+ @border-color-divider: var(--border-color-divider, #a2a9b1);
+-@border-radius-base: 2px;
++@border-radius-base: 4px;
+ @border-radius-sharp: 0;
+ @border-radius-pill: 9999px;
+ @border-radius-circle: 50%; // Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500
+ @outline-color-progressive--focus: var(
diff --git a/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.scss.patch b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.scss.patch
new file mode 100644
index 0000000..8fea899
--- /dev/null
+++ b/patches/codex/wikimedia__codex-design-tokens__theme-wikimedia-ui.scss.patch
@@ -0,0 +1,15 @@
+Index: theme-wikimedia-ui.scss
+===================================================================
+--- theme-wikimedia-ui.scss
++++ theme-wikimedia-ui.scss
+@@ -361,9 +361,9 @@
+ $border-color-option-pink: #b5739e;
+ $border-color-option-maroon: #b57775;
+ $border-color-transparent: transparent;
+ $border-color-divider: #a2a9b1;
+-$border-radius-base: 2px;
++$border-radius-base: 4px;
+ $border-radius-sharp: 0;
+ $border-radius-pill: 9999px;
+ $border-radius-circle: 50%; // Use `50%` for circle or ellipsis. See https://stackoverflow.com/a/29966500
+ $outline-color-progressive--focus: #36c; // Use in places where no more customized focus styles are provided, for example on generic `:focus`.
diff --git a/patches/codex/wikimedia__codex__dist__codex.style-bidi.css.patch b/patches/codex/wikimedia__codex__dist__codex.style-bidi.css.patch
new file mode 100644
index 0000000..61e9e4d
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__codex.style-bidi.css.patch
@@ -0,0 +1,320 @@
+Index: dist/codex.style-bidi.css
+===================================================================
+--- dist/codex.style-bidi.css
++++ dist/codex.style-bidi.css
+@@ -50,9 +50,9 @@
+ [dir] .cdx-button {
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ transition-property: background-color, color, border-color, box-shadow;
+ transition-duration: 0.1s;
+@@ -720,17 +720,17 @@
+ }
+ [dir] .cdx-accordion--separation-outline {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px 2px 0 0;
++ border-radius: 4px 4px 0 0;
+ }
+ [dir] .cdx-accordion + .cdx-accordion--separation-outline {
+ border-radius: 0;
+ }
+ [dir] .cdx-accordion--separation-outline:not(:has(+ .cdx-accordion)),
+ [dir] .cdx-accordion + .cdx-accordion--separation-outline:last-child {
+- border-bottom-left-radius: 2px;
+- border-bottom-right-radius: 2px;
++ border-bottom-left-radius: 4px;
++ border-bottom-right-radius: 4px;
+ }
+ [dir] .cdx-accordion--separation-minimal > summary {
+ padding: 6px 0;
+ }
+@@ -767,9 +767,9 @@
+ width: fit-content;
+ overflow: hidden;
+ }
+ [dir] .cdx-button-group {
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ }
+ [dir='ltr'] .cdx-button-group {
+ padding-left: 1px;
+@@ -807,8 +807,24 @@
+ box-shadow:
+ 0 -1px 0 0 var(--box-shadow-color-inverted, #fff),
+ 1px 0 0 0 var(--box-shadow-color-inverted, #fff);
+ }
++[dir='ltr'] .cdx-button-group .cdx-button:first-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
++[dir='rtl'] .cdx-button-group .cdx-button:first-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
++[dir='ltr'] .cdx-button-group .cdx-button:last-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
++[dir='rtl'] .cdx-button-group .cdx-button:last-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
+ .cdx-thumbnail {
+ display: inline-flex;
+ }
+ .cdx-thumbnail__placeholder,
+@@ -825,9 +841,9 @@
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-thumbnail__image {
+ display: inline-block;
+ }
+@@ -902,9 +918,9 @@
+ }
+ [dir] .cdx-card {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ [dir] .cdx-card--is-link {
+ transition-property: background-color, color, border-color, box-shadow;
+@@ -1171,9 +1187,9 @@
+ [dir='rtl'] .cdx-checkbox__custom-input:not(.cdx-checkbox__custom-input--inline) {
+ padding-right: calc(var(--font-size-medium, 1rem) + 10px);
+ }
+ [dir] .cdx-checkbox__icon {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-checkbox__input:indeterminate + .cdx-checkbox__icon:before {
+ content: ' ';
+ position: absolute;
+@@ -1341,9 +1357,9 @@
+ line-height: var(--line-height-small, 1.375rem);
+ }
+ [dir] .cdx-tooltip {
+ background-color: var(--background-color-inverted, #101418);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 2px 6px;
+ animation-name: cdx-animation-tooltip;
+ animation-duration: 0.1s;
+ animation-timing-function: linear;
+@@ -1446,9 +1462,9 @@
+ min-height: 32px;
+ overflow: hidden;
+ }
+ [dir] .cdx-chip-input {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-chip-input__chips,
+ .cdx-chip-input__separate-input {
+ align-items: center;
+@@ -1959,9 +1975,9 @@
+ border: 1px solid var(--border-color-progressive, #36c);
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--border-color-progressive, #36c);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ [dir] .cdx-progress-bar:not(.cdx-progress-bar--inline).cdx-progress-bar--disabled {
+ border-color: var(--border-color-disabled, #c8ccd1);
+ }
+@@ -2016,9 +2032,9 @@
+ }
+ [dir] .cdx-menu {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
+@@ -2102,9 +2118,9 @@
+ min-width: 256px;
+ overflow: hidden;
+ }
+ [dir] .cdx-text-input {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-text-input .cdx-text-input__start-icon {
+ position: absolute;
+ top: 50%;
+@@ -2421,9 +2437,9 @@
+ }
+ [dir] .cdx-dialog {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 16px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
+@@ -2578,9 +2594,9 @@
+ }
+ [dir] .cdx-message {
+ background-color: var(--background-color-notice-subtle, #eaecf0);
+ border: 1px solid var(--border-color-notice, #72777d);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-message__icon,
+ .cdx-message__icon--vue {
+@@ -3031,9 +3047,9 @@
+ }
+ [dir] .cdx-image__image {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__image--object-fit-fill {
+ object-fit: fill;
+ }
+@@ -3082,9 +3098,9 @@
+ }
+ [dir] .cdx-image__placeholder {
+ background-color: var(--background-color-interactive-subtle, #f8f9fa);
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__placeholder__icon--size-smallest {
+ width: 0.75rem;
+ height: 0.75rem;
+@@ -3608,9 +3624,9 @@
+ }
+ [dir] .cdx-popover {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 16px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+@@ -3725,12 +3741,12 @@
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
+ [dir='ltr'] .cdx-popover__arrow {
+- border-top-left-radius: 2px;
++ border-top-left-radius: 4px;
+ }
+ [dir='rtl'] .cdx-popover__arrow {
+- border-top-right-radius: 2px;
++ border-top-right-radius: 4px;
+ }
+ .cdx-popover--bottom-sheet {
+ position: static;
+ max-height: calc(100% - 8rem);
+@@ -4137,9 +4153,9 @@
+ }
+ [dir] .cdx-search-input--has-end-button {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-search-input--has-end-button .cdx-search-input__input-wrapper {
+ flex-grow: 1;
+ }
+@@ -4229,9 +4245,9 @@
+ }
+ [dir] .cdx-select {
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ background-repeat: no-repeat;
+ background-size: max(calc(var(--font-size-medium, 1rem) - 4px), 10px);
+@@ -4307,9 +4323,9 @@
+ }
+ [dir] .cdx-select-vue__handle {
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ }
+ [dir='ltr'] .cdx-select-vue__handle {
+@@ -4705,9 +4721,9 @@
+ word-wrap: break-word;
+ }
+ [dir] .cdx-table {
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ @supports (word-break: break-word) {
+ .cdx-table {
+ word-wrap: unset;
+@@ -5177,10 +5193,10 @@
+ }
+ [dir] .cdx-tabs__list__item {
+ background-color: var(--background-color-transparent, transparent);
+ border-width: 0;
+- border-top-left-radius: 2px;
+- border-top-right-radius: 2px;
++ border-top-left-radius: 4px;
++ border-top-right-radius: 4px;
+ padding: 4px 12px;
+ transition-property: background-color, color, border-color, box-shadow;
+ transition-duration: 0.1s;
+ }
+@@ -5470,9 +5486,9 @@
+ }
+ [dir] .cdx-text-area__textarea {
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 4px 8px;
+ }
+ .cdx-text-area__textarea--is-autosize {
+ resize: none;
+@@ -5720,9 +5736,9 @@
+ [dir] .cdx-toggle-button {
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ transition-property: background-color, color, border-color, box-shadow;
+ transition-duration: 0.1s;
+@@ -5869,9 +5885,9 @@
+ width: fit-content;
+ overflow: hidden;
+ }
+ [dir] .cdx-toggle-button-group {
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ }
+ [dir='ltr'] .cdx-toggle-button-group {
+ padding-left: 1px;
diff --git a/patches/codex/wikimedia__codex__dist__codex.style-rtl.css.patch b/patches/codex/wikimedia__codex__dist__codex.style-rtl.css.patch
new file mode 100644
index 0000000..62d3467
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__codex.style-rtl.css.patch
@@ -0,0 +1,308 @@
+Index: dist/codex.style-rtl.css
+===================================================================
+--- dist/codex.style-rtl.css
++++ dist/codex.style-rtl.css
+@@ -41,9 +41,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-left: 11px;
+ padding-right: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
+@@ -573,17 +573,17 @@
+ }
+ .cdx-accordion--separation-outline {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px 2px 0 0;
++ border-radius: 4px 4px 0 0;
+ }
+ .cdx-accordion + .cdx-accordion--separation-outline {
+ border-radius: 0;
+ }
+ .cdx-accordion--separation-outline:not(:has(+ .cdx-accordion)),
+ .cdx-accordion + .cdx-accordion--separation-outline:last-child {
+- border-bottom-right-radius: 2px;
+- border-bottom-left-radius: 2px;
++ border-bottom-right-radius: 4px;
++ border-bottom-left-radius: 4px;
+ }
+ .cdx-accordion--separation-minimal > summary {
+ padding: 6px 0;
+ }
+@@ -613,9 +613,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-right: 1px;
+ overflow: hidden;
+ }
+@@ -635,8 +635,16 @@
+ box-shadow:
+ 0 -1px 0 0 var(--box-shadow-color-inverted, #fff),
+ 1px 0 0 0 var(--box-shadow-color-inverted, #fff);
+ }
++.cdx-button-group .cdx-button:first-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
++.cdx-button-group .cdx-button:last-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
+ .cdx-thumbnail {
+ display: inline-flex;
+ }
+ .cdx-thumbnail__placeholder,
+@@ -650,9 +658,9 @@
+ min-height: 40px;
+ width: 2.5rem;
+ height: 2.5rem;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-thumbnail__image {
+ background-color: var(--background-color-base-fixed, #fff);
+ display: inline-block;
+@@ -717,9 +725,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-card--is-link {
+ transition-property: background-color, color, border-color, box-shadow;
+@@ -930,9 +938,9 @@
+ padding-top: 6px;
+ padding-right: calc(var(--font-size-medium, 1rem) + 10px);
+ }
+ .cdx-checkbox__icon {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-checkbox__input:indeterminate + .cdx-checkbox__icon:before {
+ content: ' ';
+ background-color: var(--background-color-base-fixed, #fff);
+@@ -1067,9 +1075,9 @@
+ z-index: 800;
+ width: -webkit-max-content;
+ width: max-content;
+ max-width: 16rem;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 2px 6px;
+ font-family:
+ -apple-system,
+ BlinkMacSystemFont,
+@@ -1169,9 +1177,9 @@
+ font-size: var(--font-size-small, 0.875rem);
+ }
+ .cdx-chip-input {
+ min-height: 32px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-chip-input__chips,
+ .cdx-chip-input__separate-input {
+@@ -1612,9 +1620,9 @@
+ border: 1px solid var(--border-color-progressive, #36c);
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--border-color-progressive, #36c);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-progress-bar:not(.cdx-progress-bar--inline).cdx-progress-bar--disabled {
+ border-color: var(--border-color-disabled, #c8ccd1);
+ }
+@@ -1654,9 +1662,9 @@
+ z-index: 50;
+ box-sizing: border-box;
+ width: 100%;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ font-size: var(--font-size-medium, 1rem);
+@@ -1729,9 +1737,9 @@
+ .cdx-text-input {
+ position: relative;
+ box-sizing: border-box;
+ min-width: 256px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-text-input .cdx-text-input__start-icon {
+ position: absolute;
+@@ -1968,9 +1976,9 @@
+ width: calc(100vw - 2rem);
+ height: unset;
+ max-height: calc(100vh - 2rem);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 16px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
+@@ -2112,9 +2120,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-notice, #72777d);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-message__icon,
+ .cdx-message__icon--vue {
+@@ -2514,9 +2522,9 @@
+ min-width: 24px;
+ min-height: 24px;
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ font-size: var(--font-size-x-small, 0.75rem);
+ }
+ .cdx-image__image--object-fit-fill {
+ object-fit: fill;
+@@ -2560,9 +2568,9 @@
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__placeholder__icon--size-smallest {
+ width: 0.75rem;
+ height: 0.75rem;
+@@ -3000,9 +3008,9 @@
+ z-index: 700;
+ box-sizing: border-box;
+ min-width: 12rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 16px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+@@ -3085,9 +3093,9 @@
+ position: absolute;
+ width: 1rem;
+ height: 1rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-top-right-radius: 2px;
++ border-top-right-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ -webkit-clip-path: polygon(0 0, 100% 0, 0 100%);
+@@ -3425,9 +3433,9 @@
+ .cdx-search-input--has-end-button {
+ background-color: var(--background-color-base, #fff);
+ display: flex;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-search-input--has-end-button .cdx-search-input__input-wrapper {
+ flex-grow: 1;
+ margin: -1px;
+@@ -3494,9 +3502,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-right: 8px;
+ padding-left: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
+@@ -3552,9 +3560,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-right: 8px;
+ padding-left: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
+@@ -3894,9 +3902,9 @@
+ }
+ .cdx-table {
+ color: var(--color-base, #202122);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ word-wrap: break-word;
+ }
+ @supports (word-break: break-word) {
+ .cdx-table {
+@@ -4282,10 +4290,10 @@
+ display: block;
+ flex: 0 0 auto;
+ max-width: 16rem;
+ border-width: 0;
+- border-top-right-radius: 2px;
+- border-top-left-radius: 2px;
++ border-top-right-radius: 4px;
++ border-top-left-radius: 4px;
+ padding: 4px 12px;
+ font-size: var(--font-size-medium, 1rem);
+ font-weight: 700;
+ line-height: var(--line-height-small, 1.375rem);
+@@ -4486,9 +4494,9 @@
+ min-height: 64px;
+ width: 100%;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 4px 8px;
+ overflow: auto;
+ font-family: sans-serif;
+ font-size: var(--font-size-medium, 1rem);
+@@ -4682,9 +4690,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-left: 11px;
+ padding-right: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
+@@ -4813,9 +4821,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-right: 1px;
+ overflow: hidden;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__codex.style.css.patch b/patches/codex/wikimedia__codex__dist__codex.style.css.patch
new file mode 100644
index 0000000..3cf6448
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__codex.style.css.patch
@@ -0,0 +1,308 @@
+Index: dist/codex.style.css
+===================================================================
+--- dist/codex.style.css
++++ dist/codex.style.css
+@@ -41,9 +41,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
+@@ -573,17 +573,17 @@
+ }
+ .cdx-accordion--separation-outline {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px 2px 0 0;
++ border-radius: 4px 4px 0 0;
+ }
+ .cdx-accordion + .cdx-accordion--separation-outline {
+ border-radius: 0;
+ }
+ .cdx-accordion--separation-outline:not(:has(+ .cdx-accordion)),
+ .cdx-accordion + .cdx-accordion--separation-outline:last-child {
+- border-bottom-left-radius: 2px;
+- border-bottom-right-radius: 2px;
++ border-bottom-left-radius: 4px;
++ border-bottom-right-radius: 4px;
+ }
+ .cdx-accordion--separation-minimal > summary {
+ padding: 6px 0;
+ }
+@@ -613,9 +613,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-left: 1px;
+ overflow: hidden;
+ }
+@@ -635,8 +635,16 @@
+ box-shadow:
+ 0 -1px 0 0 var(--box-shadow-color-inverted, #fff),
+ -1px 0 0 0 var(--box-shadow-color-inverted, #fff);
+ }
++.cdx-button-group .cdx-button:first-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
++.cdx-button-group .cdx-button:last-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
+ .cdx-thumbnail {
+ display: inline-flex;
+ }
+ .cdx-thumbnail__placeholder,
+@@ -650,9 +658,9 @@
+ min-height: 40px;
+ width: 2.5rem;
+ height: 2.5rem;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-thumbnail__image {
+ background-color: var(--background-color-base-fixed, #fff);
+ display: inline-block;
+@@ -717,9 +725,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-card--is-link {
+ transition-property: background-color, color, border-color, box-shadow;
+@@ -930,9 +938,9 @@
+ padding-top: 6px;
+ padding-left: calc(var(--font-size-medium, 1rem) + 10px);
+ }
+ .cdx-checkbox__icon {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-checkbox__input:indeterminate + .cdx-checkbox__icon:before {
+ content: ' ';
+ background-color: var(--background-color-base-fixed, #fff);
+@@ -1067,9 +1075,9 @@
+ z-index: 800;
+ width: -webkit-max-content;
+ width: max-content;
+ max-width: 16rem;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 2px 6px;
+ font-family:
+ -apple-system,
+ BlinkMacSystemFont,
+@@ -1169,9 +1177,9 @@
+ font-size: var(--font-size-small, 0.875rem);
+ }
+ .cdx-chip-input {
+ min-height: 32px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-chip-input__chips,
+ .cdx-chip-input__separate-input {
+@@ -1612,9 +1620,9 @@
+ border: 1px solid var(--border-color-progressive, #36c);
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--border-color-progressive, #36c);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-progress-bar:not(.cdx-progress-bar--inline).cdx-progress-bar--disabled {
+ border-color: var(--border-color-disabled, #c8ccd1);
+ }
+@@ -1654,9 +1662,9 @@
+ z-index: 50;
+ box-sizing: border-box;
+ width: 100%;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ font-size: var(--font-size-medium, 1rem);
+@@ -1729,9 +1737,9 @@
+ .cdx-text-input {
+ position: relative;
+ box-sizing: border-box;
+ min-width: 256px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-text-input .cdx-text-input__start-icon {
+ position: absolute;
+@@ -1968,9 +1976,9 @@
+ width: calc(100vw - 2rem);
+ height: unset;
+ max-height: calc(100vh - 2rem);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 16px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
+@@ -2112,9 +2120,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-notice, #72777d);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-message__icon,
+ .cdx-message__icon--vue {
+@@ -2514,9 +2522,9 @@
+ min-width: 24px;
+ min-height: 24px;
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ font-size: var(--font-size-x-small, 0.75rem);
+ }
+ .cdx-image__image--object-fit-fill {
+ object-fit: fill;
+@@ -2560,9 +2568,9 @@
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__placeholder__icon--size-smallest {
+ width: 0.75rem;
+ height: 0.75rem;
+@@ -3000,9 +3008,9 @@
+ z-index: 700;
+ box-sizing: border-box;
+ min-width: 12rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 16px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+@@ -3085,9 +3093,9 @@
+ position: absolute;
+ width: 1rem;
+ height: 1rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-top-left-radius: 2px;
++ border-top-left-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ -webkit-clip-path: polygon(0 0, 100% 0, 0 100%);
+@@ -3425,9 +3433,9 @@
+ .cdx-search-input--has-end-button {
+ background-color: var(--background-color-base, #fff);
+ display: flex;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-search-input--has-end-button .cdx-search-input__input-wrapper {
+ flex-grow: 1;
+ margin: -1px;
+@@ -3494,9 +3502,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 8px;
+ padding-right: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
+@@ -3552,9 +3560,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 8px;
+ padding-right: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
+@@ -3894,9 +3902,9 @@
+ }
+ .cdx-table {
+ color: var(--color-base, #202122);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ word-wrap: break-word;
+ }
+ @supports (word-break: break-word) {
+ .cdx-table {
+@@ -4282,10 +4290,10 @@
+ display: block;
+ flex: 0 0 auto;
+ max-width: 16rem;
+ border-width: 0;
+- border-top-left-radius: 2px;
+- border-top-right-radius: 2px;
++ border-top-left-radius: 4px;
++ border-top-right-radius: 4px;
+ padding: 4px 12px;
+ font-size: var(--font-size-medium, 1rem);
+ font-weight: 700;
+ line-height: var(--line-height-small, 1.375rem);
+@@ -4486,9 +4494,9 @@
+ min-height: 64px;
+ width: 100%;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 4px 8px;
+ overflow: auto;
+ font-family: sans-serif;
+ font-size: var(--font-size-medium, 1rem);
+@@ -4682,9 +4690,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
+@@ -4813,9 +4821,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-left: 1px;
+ overflow: hidden;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxAccordion-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxAccordion-bidi.css.patch
new file mode 100644
index 0000000..a7f2a8a
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxAccordion-bidi.css.patch
@@ -0,0 +1,25 @@
+Index: dist/modules/CdxAccordion-bidi.css
+===================================================================
+--- dist/modules/CdxAccordion-bidi.css
++++ dist/modules/CdxAccordion-bidi.css
+@@ -155,17 +155,17 @@
+ }
+ [dir] .cdx-accordion--separation-outline {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px 2px 0 0;
++ border-radius: 4px 4px 0 0;
+ }
+ [dir] .cdx-accordion + .cdx-accordion--separation-outline {
+ border-radius: 0;
+ }
+ [dir] .cdx-accordion--separation-outline:not(:has(+ .cdx-accordion)),
+ [dir] .cdx-accordion + .cdx-accordion--separation-outline:last-child {
+- border-bottom-left-radius: 2px;
+- border-bottom-right-radius: 2px;
++ border-bottom-left-radius: 4px;
++ border-bottom-right-radius: 4px;
+ }
+ [dir] .cdx-accordion--separation-minimal > summary {
+ padding: 6px 0;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxAccordion-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxAccordion-rtl.css.patch
new file mode 100644
index 0000000..d29c1cc
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxAccordion-rtl.css.patch
@@ -0,0 +1,25 @@
+Index: dist/modules/CdxAccordion-rtl.css
+===================================================================
+--- dist/modules/CdxAccordion-rtl.css
++++ dist/modules/CdxAccordion-rtl.css
+@@ -136,17 +136,17 @@
+ }
+ .cdx-accordion--separation-outline {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px 2px 0 0;
++ border-radius: 4px 4px 0 0;
+ }
+ .cdx-accordion + .cdx-accordion--separation-outline {
+ border-radius: 0;
+ }
+ .cdx-accordion--separation-outline:not(:has(+ .cdx-accordion)),
+ .cdx-accordion + .cdx-accordion--separation-outline:last-child {
+- border-bottom-right-radius: 2px;
+- border-bottom-left-radius: 2px;
++ border-bottom-right-radius: 4px;
++ border-bottom-left-radius: 4px;
+ }
+ .cdx-accordion--separation-minimal > summary {
+ padding: 6px 0;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxAccordion.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxAccordion.css.patch
new file mode 100644
index 0000000..68e2f69
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxAccordion.css.patch
@@ -0,0 +1,25 @@
+Index: dist/modules/CdxAccordion.css
+===================================================================
+--- dist/modules/CdxAccordion.css
++++ dist/modules/CdxAccordion.css
+@@ -136,17 +136,17 @@
+ }
+ .cdx-accordion--separation-outline {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px 2px 0 0;
++ border-radius: 4px 4px 0 0;
+ }
+ .cdx-accordion + .cdx-accordion--separation-outline {
+ border-radius: 0;
+ }
+ .cdx-accordion--separation-outline:not(:has(+ .cdx-accordion)),
+ .cdx-accordion + .cdx-accordion--separation-outline:last-child {
+- border-bottom-left-radius: 2px;
+- border-bottom-right-radius: 2px;
++ border-bottom-left-radius: 4px;
++ border-bottom-right-radius: 4px;
+ }
+ .cdx-accordion--separation-minimal > summary {
+ padding: 6px 0;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxButton-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxButton-bidi.css.patch
new file mode 100644
index 0000000..42ae44b
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxButton-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxButton-bidi.css
+===================================================================
+--- dist/modules/CdxButton-bidi.css
++++ dist/modules/CdxButton-bidi.css
+@@ -17,9 +17,9 @@
+ [dir] .cdx-button {
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ transition-property: background-color, color, border-color, box-shadow;
+ transition-duration: 0.1s;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxButton-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxButton-rtl.css.patch
new file mode 100644
index 0000000..5f8f1bd
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxButton-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxButton-rtl.css
+===================================================================
+--- dist/modules/CdxButton-rtl.css
++++ dist/modules/CdxButton-rtl.css
+@@ -8,9 +8,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-left: 11px;
+ padding-right: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxButton.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxButton.css.patch
new file mode 100644
index 0000000..29b4f79
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxButton.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxButton.css
+===================================================================
+--- dist/modules/CdxButton.css
++++ dist/modules/CdxButton.css
+@@ -8,9 +8,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-bidi.css.patch
new file mode 100644
index 0000000..dfc8acb
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-bidi.css.patch
@@ -0,0 +1,36 @@
+Index: dist/modules/CdxButtonGroup-bidi.css
+===================================================================
+--- dist/modules/CdxButtonGroup-bidi.css
++++ dist/modules/CdxButtonGroup-bidi.css
+@@ -5,9 +5,9 @@
+ width: fit-content;
+ overflow: hidden;
+ }
+ [dir] .cdx-button-group {
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ }
+ [dir='ltr'] .cdx-button-group {
+ padding-left: 1px;
+@@ -45,4 +45,20 @@
+ box-shadow:
+ 0 -1px 0 0 var(--box-shadow-color-inverted, #fff),
+ 1px 0 0 0 var(--box-shadow-color-inverted, #fff);
+ }
++[dir='ltr'] .cdx-button-group .cdx-button:first-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
++[dir='rtl'] .cdx-button-group .cdx-button:first-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
++[dir='ltr'] .cdx-button-group .cdx-button:last-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
++[dir='rtl'] .cdx-button-group .cdx-button:last-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-rtl.css.patch
new file mode 100644
index 0000000..39283a8
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup-rtl.css.patch
@@ -0,0 +1,28 @@
+Index: dist/modules/CdxButtonGroup-rtl.css
+===================================================================
+--- dist/modules/CdxButtonGroup-rtl.css
++++ dist/modules/CdxButtonGroup-rtl.css
+@@ -2,9 +2,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-right: 1px;
+ overflow: hidden;
+ }
+@@ -24,4 +24,12 @@
+ box-shadow:
+ 0 -1px 0 0 var(--box-shadow-color-inverted, #fff),
+ 1px 0 0 0 var(--box-shadow-color-inverted, #fff);
+ }
++.cdx-button-group .cdx-button:first-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
++.cdx-button-group .cdx-button:last-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup.css.patch
new file mode 100644
index 0000000..337060f
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxButtonGroup.css.patch
@@ -0,0 +1,28 @@
+Index: dist/modules/CdxButtonGroup.css
+===================================================================
+--- dist/modules/CdxButtonGroup.css
++++ dist/modules/CdxButtonGroup.css
+@@ -2,9 +2,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-left: 1px;
+ overflow: hidden;
+ }
+@@ -24,4 +24,12 @@
+ box-shadow:
+ 0 -1px 0 0 var(--box-shadow-color-inverted, #fff),
+ -1px 0 0 0 var(--box-shadow-color-inverted, #fff);
+ }
++.cdx-button-group .cdx-button:first-child {
++ border-top-left-radius: inherit;
++ border-bottom-left-radius: inherit;
++}
++.cdx-button-group .cdx-button:last-child {
++ border-top-right-radius: inherit;
++ border-bottom-right-radius: inherit;
++}
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxCard-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxCard-bidi.css.patch
new file mode 100644
index 0000000..b2d71f0
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxCard-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxCard-bidi.css
+===================================================================
+--- dist/modules/CdxCard-bidi.css
++++ dist/modules/CdxCard-bidi.css
+@@ -5,9 +5,9 @@
+ }
+ [dir] .cdx-card {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ [dir] .cdx-card--is-link {
+ transition-property: background-color, color, border-color, box-shadow;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxCard-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxCard-rtl.css.patch
new file mode 100644
index 0000000..e11d120
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxCard-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxCard-rtl.css
+===================================================================
+--- dist/modules/CdxCard-rtl.css
++++ dist/modules/CdxCard-rtl.css
+@@ -3,9 +3,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-card--is-link {
+ transition-property: background-color, color, border-color, box-shadow;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxCard.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxCard.css.patch
new file mode 100644
index 0000000..c0efe72
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxCard.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxCard.css
+===================================================================
+--- dist/modules/CdxCard.css
++++ dist/modules/CdxCard.css
+@@ -3,9 +3,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-card--is-link {
+ transition-property: background-color, color, border-color, box-shadow;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-bidi.css.patch
new file mode 100644
index 0000000..d308d07
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxCheckbox-bidi.css
+===================================================================
+--- dist/modules/CdxCheckbox-bidi.css
++++ dist/modules/CdxCheckbox-bidi.css
+@@ -114,9 +114,9 @@
+ [dir='rtl'] .cdx-checkbox__custom-input:not(.cdx-checkbox__custom-input--inline) {
+ padding-right: calc(var(--font-size-medium, 1rem) + 10px);
+ }
+ [dir] .cdx-checkbox__icon {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-checkbox__input:indeterminate + .cdx-checkbox__icon:before {
+ content: ' ';
+ position: absolute;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-rtl.css.patch
new file mode 100644
index 0000000..a97f1bd
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxCheckbox-rtl.css
+===================================================================
+--- dist/modules/CdxCheckbox-rtl.css
++++ dist/modules/CdxCheckbox-rtl.css
+@@ -75,9 +75,9 @@
+ padding-top: 6px;
+ padding-right: calc(var(--font-size-medium, 1rem) + 10px);
+ }
+ .cdx-checkbox__icon {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-checkbox__input:indeterminate + .cdx-checkbox__icon:before {
+ content: ' ';
+ background-color: var(--background-color-base-fixed, #fff);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox.css.patch
new file mode 100644
index 0000000..b5c7721
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxCheckbox.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxCheckbox.css
+===================================================================
+--- dist/modules/CdxCheckbox.css
++++ dist/modules/CdxCheckbox.css
+@@ -75,9 +75,9 @@
+ padding-top: 6px;
+ padding-left: calc(var(--font-size-medium, 1rem) + 10px);
+ }
+ .cdx-checkbox__icon {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-checkbox__input:indeterminate + .cdx-checkbox__icon:before {
+ content: ' ';
+ background-color: var(--background-color-base-fixed, #fff);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxChipInput-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxChipInput-bidi.css.patch
new file mode 100644
index 0000000..443d696
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxChipInput-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxChipInput-bidi.css
+===================================================================
+--- dist/modules/CdxChipInput-bidi.css
++++ dist/modules/CdxChipInput-bidi.css
+@@ -84,9 +84,9 @@
+ min-height: 32px;
+ overflow: hidden;
+ }
+ [dir] .cdx-chip-input {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-chip-input__chips,
+ .cdx-chip-input__separate-input {
+ align-items: center;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxChipInput-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxChipInput-rtl.css.patch
new file mode 100644
index 0000000..8ec1a05
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxChipInput-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxChipInput-rtl.css
+===================================================================
+--- dist/modules/CdxChipInput-rtl.css
++++ dist/modules/CdxChipInput-rtl.css
+@@ -69,9 +69,9 @@
+ font-size: var(--font-size-small, 0.875rem);
+ }
+ .cdx-chip-input {
+ min-height: 32px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-chip-input__chips,
+ .cdx-chip-input__separate-input {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxChipInput.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxChipInput.css.patch
new file mode 100644
index 0000000..f8394f4
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxChipInput.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxChipInput.css
+===================================================================
+--- dist/modules/CdxChipInput.css
++++ dist/modules/CdxChipInput.css
+@@ -69,9 +69,9 @@
+ font-size: var(--font-size-small, 0.875rem);
+ }
+ .cdx-chip-input {
+ min-height: 32px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-chip-input__chips,
+ .cdx-chip-input__separate-input {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxDialog-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxDialog-bidi.css.patch
new file mode 100644
index 0000000..61792e0
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxDialog-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxDialog-bidi.css
+===================================================================
+--- dist/modules/CdxDialog-bidi.css
++++ dist/modules/CdxDialog-bidi.css
+@@ -29,9 +29,9 @@
+ }
+ [dir] .cdx-dialog {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 16px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxDialog-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxDialog-rtl.css.patch
new file mode 100644
index 0000000..cc8ec64
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxDialog-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxDialog-rtl.css
+===================================================================
+--- dist/modules/CdxDialog-rtl.css
++++ dist/modules/CdxDialog-rtl.css
+@@ -20,9 +20,9 @@
+ width: calc(100vw - 2rem);
+ height: unset;
+ max-height: calc(100vh - 2rem);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 16px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxDialog.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxDialog.css.patch
new file mode 100644
index 0000000..510505b
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxDialog.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxDialog.css
+===================================================================
+--- dist/modules/CdxDialog.css
++++ dist/modules/CdxDialog.css
+@@ -20,9 +20,9 @@
+ width: calc(100vw - 2rem);
+ height: unset;
+ max-height: calc(100vh - 2rem);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 16px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxImage-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxImage-bidi.css.patch
new file mode 100644
index 0000000..f117ffd
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxImage-bidi.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxImage-bidi.css
+===================================================================
+--- dist/modules/CdxImage-bidi.css
++++ dist/modules/CdxImage-bidi.css
+@@ -22,9 +22,9 @@
+ }
+ [dir] .cdx-image__image {
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__image--object-fit-fill {
+ object-fit: fill;
+ }
+@@ -73,9 +73,9 @@
+ }
+ [dir] .cdx-image__placeholder {
+ background-color: var(--background-color-interactive-subtle, #f8f9fa);
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__placeholder__icon--size-smallest {
+ width: 0.75rem;
+ height: 0.75rem;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxImage-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxImage-rtl.css.patch
new file mode 100644
index 0000000..8ba77c4
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxImage-rtl.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxImage-rtl.css
+===================================================================
+--- dist/modules/CdxImage-rtl.css
++++ dist/modules/CdxImage-rtl.css
+@@ -19,9 +19,9 @@
+ min-width: 24px;
+ min-height: 24px;
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ font-size: var(--font-size-x-small, 0.75rem);
+ }
+ .cdx-image__image--object-fit-fill {
+ object-fit: fill;
+@@ -65,9 +65,9 @@
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__placeholder__icon--size-smallest {
+ width: 0.75rem;
+ height: 0.75rem;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxImage.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxImage.css.patch
new file mode 100644
index 0000000..204d46e
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxImage.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxImage.css
+===================================================================
+--- dist/modules/CdxImage.css
++++ dist/modules/CdxImage.css
+@@ -19,9 +19,9 @@
+ min-width: 24px;
+ min-height: 24px;
+ margin: 0;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ font-size: var(--font-size-x-small, 0.75rem);
+ }
+ .cdx-image__image--object-fit-fill {
+ object-fit: fill;
+@@ -65,9 +65,9 @@
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-image__placeholder__icon--size-smallest {
+ width: 0.75rem;
+ height: 0.75rem;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxMenu-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxMenu-bidi.css.patch
new file mode 100644
index 0000000..3094319
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxMenu-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxMenu-bidi.css
+===================================================================
+--- dist/modules/CdxMenu-bidi.css
++++ dist/modules/CdxMenu-bidi.css
+@@ -10,9 +10,9 @@
+ }
+ [dir] .cdx-menu {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxMenu-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxMenu-rtl.css.patch
new file mode 100644
index 0000000..be49cf0
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxMenu-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxMenu-rtl.css
+===================================================================
+--- dist/modules/CdxMenu-rtl.css
++++ dist/modules/CdxMenu-rtl.css
+@@ -7,9 +7,9 @@
+ z-index: 50;
+ box-sizing: border-box;
+ width: 100%;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxMenu.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxMenu.css.patch
new file mode 100644
index 0000000..42b184e
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxMenu.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxMenu.css
+===================================================================
+--- dist/modules/CdxMenu.css
++++ dist/modules/CdxMenu.css
+@@ -7,9 +7,9 @@
+ z-index: 50;
+ box-sizing: border-box;
+ width: 100%;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxMessage-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxMessage-bidi.css.patch
new file mode 100644
index 0000000..5c2760e
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxMessage-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxMessage-bidi.css
+===================================================================
+--- dist/modules/CdxMessage-bidi.css
++++ dist/modules/CdxMessage-bidi.css
+@@ -6,9 +6,9 @@
+ }
+ [dir] .cdx-message {
+ background-color: var(--background-color-notice-subtle, #eaecf0);
+ border: 1px solid var(--border-color-notice, #72777d);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-message__icon,
+ .cdx-message__icon--vue {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxMessage-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxMessage-rtl.css.patch
new file mode 100644
index 0000000..116e559
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxMessage-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxMessage-rtl.css
+===================================================================
+--- dist/modules/CdxMessage-rtl.css
++++ dist/modules/CdxMessage-rtl.css
+@@ -4,9 +4,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-notice, #72777d);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-message__icon,
+ .cdx-message__icon--vue {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxMessage.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxMessage.css.patch
new file mode 100644
index 0000000..06d9e2a
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxMessage.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxMessage.css
+===================================================================
+--- dist/modules/CdxMessage.css
++++ dist/modules/CdxMessage.css
+@@ -4,9 +4,9 @@
+ display: flex;
+ align-items: flex-start;
+ position: relative;
+ border: 1px solid var(--border-color-notice, #72777d);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 12px;
+ }
+ .cdx-message__icon,
+ .cdx-message__icon--vue {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxPopover-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxPopover-bidi.css.patch
new file mode 100644
index 0000000..42291c8
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxPopover-bidi.css.patch
@@ -0,0 +1,30 @@
+Index: dist/modules/CdxPopover-bidi.css
+===================================================================
+--- dist/modules/CdxPopover-bidi.css
++++ dist/modules/CdxPopover-bidi.css
+@@ -39,9 +39,9 @@
+ }
+ [dir] .cdx-popover {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 16px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+@@ -156,12 +156,12 @@
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ }
+ [dir='ltr'] .cdx-popover__arrow {
+- border-top-left-radius: 2px;
++ border-top-left-radius: 4px;
+ }
+ [dir='rtl'] .cdx-popover__arrow {
+- border-top-right-radius: 2px;
++ border-top-right-radius: 4px;
+ }
+ .cdx-popover--bottom-sheet {
+ position: static;
+ max-height: calc(100% - 8rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxPopover-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxPopover-rtl.css.patch
new file mode 100644
index 0000000..fee00dd
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxPopover-rtl.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxPopover-rtl.css
+===================================================================
+--- dist/modules/CdxPopover-rtl.css
++++ dist/modules/CdxPopover-rtl.css
+@@ -30,9 +30,9 @@
+ z-index: 700;
+ box-sizing: border-box;
+ min-width: 12rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 16px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+@@ -115,9 +115,9 @@
+ position: absolute;
+ width: 1rem;
+ height: 1rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-top-right-radius: 2px;
++ border-top-right-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ -webkit-clip-path: polygon(0 0, 100% 0, 0 100%);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxPopover.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxPopover.css.patch
new file mode 100644
index 0000000..9b92fc2
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxPopover.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxPopover.css
+===================================================================
+--- dist/modules/CdxPopover.css
++++ dist/modules/CdxPopover.css
+@@ -30,9 +30,9 @@
+ z-index: 700;
+ box-sizing: border-box;
+ min-width: 12rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 16px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+@@ -115,9 +115,9 @@
+ position: absolute;
+ width: 1rem;
+ height: 1rem;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-top-left-radius: 2px;
++ border-top-left-radius: 4px;
+ box-shadow:
+ 0 4px 4px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06)),
+ 0 0 8px 0 var(--box-shadow-color-alpha-base, rgba(0, 0, 0, 0.06));
+ -webkit-clip-path: polygon(0 0, 100% 0, 0 100%);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-bidi.css.patch
new file mode 100644
index 0000000..819dc2b
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxProgressBar-bidi.css
+===================================================================
+--- dist/modules/CdxProgressBar-bidi.css
++++ dist/modules/CdxProgressBar-bidi.css
+@@ -43,9 +43,9 @@
+ border: 1px solid var(--border-color-progressive, #36c);
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--border-color-progressive, #36c);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ [dir] .cdx-progress-bar:not(.cdx-progress-bar--inline).cdx-progress-bar--disabled {
+ border-color: var(--border-color-disabled, #c8ccd1);
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-rtl.css.patch
new file mode 100644
index 0000000..1a5fd86
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxProgressBar-rtl.css
+===================================================================
+--- dist/modules/CdxProgressBar-rtl.css
++++ dist/modules/CdxProgressBar-rtl.css
+@@ -29,9 +29,9 @@
+ border: 1px solid var(--border-color-progressive, #36c);
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--border-color-progressive, #36c);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-progress-bar:not(.cdx-progress-bar--inline).cdx-progress-bar--disabled {
+ border-color: var(--border-color-disabled, #c8ccd1);
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar.css.patch
new file mode 100644
index 0000000..e80ded7
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxProgressBar.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxProgressBar.css
+===================================================================
+--- dist/modules/CdxProgressBar.css
++++ dist/modules/CdxProgressBar.css
+@@ -29,9 +29,9 @@
+ border: 1px solid var(--border-color-progressive, #36c);
+ border-width: 1px;
+ border-style: solid;
+ border-color: var(--border-color-progressive, #36c);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-progress-bar:not(.cdx-progress-bar--inline).cdx-progress-bar--disabled {
+ border-color: var(--border-color-disabled, #c8ccd1);
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-bidi.css.patch
new file mode 100644
index 0000000..3ada8cb
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxSearchInput-bidi.css
+===================================================================
+--- dist/modules/CdxSearchInput-bidi.css
++++ dist/modules/CdxSearchInput-bidi.css
+@@ -3,9 +3,9 @@
+ }
+ [dir] .cdx-search-input--has-end-button {
+ background-color: var(--background-color-base, #fff);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-search-input--has-end-button .cdx-search-input__input-wrapper {
+ flex-grow: 1;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-rtl.css.patch
new file mode 100644
index 0000000..9894712
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxSearchInput-rtl.css
+===================================================================
+--- dist/modules/CdxSearchInput-rtl.css
++++ dist/modules/CdxSearchInput-rtl.css
+@@ -1,9 +1,9 @@
+ .cdx-search-input--has-end-button {
+ background-color: var(--background-color-base, #fff);
+ display: flex;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-search-input--has-end-button .cdx-search-input__input-wrapper {
+ flex-grow: 1;
+ margin: -1px;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput.css.patch
new file mode 100644
index 0000000..da37d85
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxSearchInput.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxSearchInput.css
+===================================================================
+--- dist/modules/CdxSearchInput.css
++++ dist/modules/CdxSearchInput.css
+@@ -1,9 +1,9 @@
+ .cdx-search-input--has-end-button {
+ background-color: var(--background-color-base, #fff);
+ display: flex;
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-search-input--has-end-button .cdx-search-input__input-wrapper {
+ flex-grow: 1;
+ margin: -1px;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxSelect-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxSelect-bidi.css.patch
new file mode 100644
index 0000000..82905b4
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxSelect-bidi.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxSelect-bidi.css
+===================================================================
+--- dist/modules/CdxSelect-bidi.css
++++ dist/modules/CdxSelect-bidi.css
+@@ -10,9 +10,9 @@
+ }
+ [dir] .cdx-select {
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ background-repeat: no-repeat;
+ background-size: max(calc(var(--font-size-medium, 1rem) - 4px), 10px);
+@@ -88,9 +88,9 @@
+ }
+ [dir] .cdx-select-vue__handle {
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ }
+ [dir='ltr'] .cdx-select-vue__handle {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxSelect-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxSelect-rtl.css.patch
new file mode 100644
index 0000000..bd29446
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxSelect-rtl.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxSelect-rtl.css
+===================================================================
+--- dist/modules/CdxSelect-rtl.css
++++ dist/modules/CdxSelect-rtl.css
+@@ -4,9 +4,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-right: 8px;
+ padding-left: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
+@@ -62,9 +62,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-right: 8px;
+ padding-left: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxSelect.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxSelect.css.patch
new file mode 100644
index 0000000..ea7f168
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxSelect.css.patch
@@ -0,0 +1,26 @@
+Index: dist/modules/CdxSelect.css
+===================================================================
+--- dist/modules/CdxSelect.css
++++ dist/modules/CdxSelect.css
+@@ -4,9 +4,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 8px;
+ padding-right: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
+@@ -62,9 +62,9 @@
+ min-width: 256px;
+ min-height: 32px;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-left: 8px;
+ padding-right: calc(8px + 8px + calc(var(--font-size-medium, 1rem) + 4px));
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTable-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTable-bidi.css.patch
new file mode 100644
index 0000000..ea698de
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTable-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTable-bidi.css
+===================================================================
+--- dist/modules/CdxTable-bidi.css
++++ dist/modules/CdxTable-bidi.css
+@@ -234,9 +234,9 @@
+ word-wrap: break-word;
+ }
+ [dir] .cdx-table {
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ @supports (word-break: break-word) {
+ .cdx-table {
+ word-wrap: unset;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTable-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTable-rtl.css.patch
new file mode 100644
index 0000000..324a70d
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTable-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTable-rtl.css
+===================================================================
+--- dist/modules/CdxTable-rtl.css
++++ dist/modules/CdxTable-rtl.css
+@@ -211,9 +211,9 @@
+ }
+ .cdx-table {
+ color: var(--color-base, #202122);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ word-wrap: break-word;
+ }
+ @supports (word-break: break-word) {
+ .cdx-table {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTable.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTable.css.patch
new file mode 100644
index 0000000..45786d3
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTable.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTable.css
+===================================================================
+--- dist/modules/CdxTable.css
++++ dist/modules/CdxTable.css
+@@ -211,9 +211,9 @@
+ }
+ .cdx-table {
+ color: var(--color-base, #202122);
+ border: 1px solid var(--border-color-base, #a2a9b1);
+- border-radius: 2px;
++ border-radius: 4px;
+ word-wrap: break-word;
+ }
+ @supports (word-break: break-word) {
+ .cdx-table {
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTabs-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTabs-bidi.css.patch
new file mode 100644
index 0000000..14db0c9
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTabs-bidi.css.patch
@@ -0,0 +1,17 @@
+Index: dist/modules/CdxTabs-bidi.css
+===================================================================
+--- dist/modules/CdxTabs-bidi.css
++++ dist/modules/CdxTabs-bidi.css
+@@ -72,10 +72,10 @@
+ }
+ [dir] .cdx-tabs__list__item {
+ background-color: var(--background-color-transparent, transparent);
+ border-width: 0;
+- border-top-left-radius: 2px;
+- border-top-right-radius: 2px;
++ border-top-left-radius: 4px;
++ border-top-right-radius: 4px;
+ padding: 4px 12px;
+ transition-property: background-color, color, border-color, box-shadow;
+ transition-duration: 0.1s;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTabs-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTabs-rtl.css.patch
new file mode 100644
index 0000000..241976f
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTabs-rtl.css.patch
@@ -0,0 +1,17 @@
+Index: dist/modules/CdxTabs-rtl.css
+===================================================================
+--- dist/modules/CdxTabs-rtl.css
++++ dist/modules/CdxTabs-rtl.css
+@@ -50,10 +50,10 @@
+ display: block;
+ flex: 0 0 auto;
+ max-width: 16rem;
+ border-width: 0;
+- border-top-right-radius: 2px;
+- border-top-left-radius: 2px;
++ border-top-right-radius: 4px;
++ border-top-left-radius: 4px;
+ padding: 4px 12px;
+ font-size: var(--font-size-medium, 1rem);
+ font-weight: 700;
+ line-height: var(--line-height-small, 1.375rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTabs.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTabs.css.patch
new file mode 100644
index 0000000..f7c6620
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTabs.css.patch
@@ -0,0 +1,17 @@
+Index: dist/modules/CdxTabs.css
+===================================================================
+--- dist/modules/CdxTabs.css
++++ dist/modules/CdxTabs.css
+@@ -50,10 +50,10 @@
+ display: block;
+ flex: 0 0 auto;
+ max-width: 16rem;
+ border-width: 0;
+- border-top-left-radius: 2px;
+- border-top-right-radius: 2px;
++ border-top-left-radius: 4px;
++ border-top-right-radius: 4px;
+ padding: 4px 12px;
+ font-size: var(--font-size-medium, 1rem);
+ font-weight: 700;
+ line-height: var(--line-height-small, 1.375rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTextArea-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTextArea-bidi.css.patch
new file mode 100644
index 0000000..7df7d68
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTextArea-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTextArea-bidi.css
+===================================================================
+--- dist/modules/CdxTextArea-bidi.css
++++ dist/modules/CdxTextArea-bidi.css
+@@ -92,9 +92,9 @@
+ }
+ [dir] .cdx-text-area__textarea {
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 4px 8px;
+ }
+ .cdx-text-area__textarea--is-autosize {
+ resize: none;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTextArea-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTextArea-rtl.css.patch
new file mode 100644
index 0000000..88a4c07
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTextArea-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTextArea-rtl.css
+===================================================================
+--- dist/modules/CdxTextArea-rtl.css
++++ dist/modules/CdxTextArea-rtl.css
+@@ -70,9 +70,9 @@
+ min-height: 64px;
+ width: 100%;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 4px 8px;
+ overflow: auto;
+ font-family: sans-serif;
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTextArea.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTextArea.css.patch
new file mode 100644
index 0000000..5bf79a8
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTextArea.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTextArea.css
+===================================================================
+--- dist/modules/CdxTextArea.css
++++ dist/modules/CdxTextArea.css
+@@ -70,9 +70,9 @@
+ min-height: 64px;
+ width: 100%;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 4px 8px;
+ overflow: auto;
+ font-family: sans-serif;
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTextInput-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTextInput-bidi.css.patch
new file mode 100644
index 0000000..4f27634
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTextInput-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTextInput-bidi.css
+===================================================================
+--- dist/modules/CdxTextInput-bidi.css
++++ dist/modules/CdxTextInput-bidi.css
+@@ -4,9 +4,9 @@
+ min-width: 256px;
+ overflow: hidden;
+ }
+ [dir] .cdx-text-input {
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-text-input .cdx-text-input__start-icon {
+ position: absolute;
+ top: 50%;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTextInput-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTextInput-rtl.css.patch
new file mode 100644
index 0000000..79cd5a2
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTextInput-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTextInput-rtl.css
+===================================================================
+--- dist/modules/CdxTextInput-rtl.css
++++ dist/modules/CdxTextInput-rtl.css
+@@ -1,9 +1,9 @@
+ .cdx-text-input {
+ position: relative;
+ box-sizing: border-box;
+ min-width: 256px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-text-input .cdx-text-input__start-icon {
+ position: absolute;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTextInput.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTextInput.css.patch
new file mode 100644
index 0000000..03d91f0
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTextInput.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTextInput.css
+===================================================================
+--- dist/modules/CdxTextInput.css
++++ dist/modules/CdxTextInput.css
+@@ -1,9 +1,9 @@
+ .cdx-text-input {
+ position: relative;
+ box-sizing: border-box;
+ min-width: 256px;
+- border-radius: 2px;
++ border-radius: 4px;
+ overflow: hidden;
+ }
+ .cdx-text-input .cdx-text-input__start-icon {
+ position: absolute;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-bidi.css.patch
new file mode 100644
index 0000000..18a9156
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxThumbnail-bidi.css
+===================================================================
+--- dist/modules/CdxThumbnail-bidi.css
++++ dist/modules/CdxThumbnail-bidi.css
+@@ -15,9 +15,9 @@
+ background-position: center;
+ background-repeat: no-repeat;
+ background-size: cover;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-thumbnail__image {
+ display: inline-block;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-rtl.css.patch
new file mode 100644
index 0000000..9b7226a
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxThumbnail-rtl.css
+===================================================================
+--- dist/modules/CdxThumbnail-rtl.css
++++ dist/modules/CdxThumbnail-rtl.css
+@@ -12,9 +12,9 @@
+ min-height: 40px;
+ width: 2.5rem;
+ height: 2.5rem;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-thumbnail__image {
+ background-color: var(--background-color-base-fixed, #fff);
+ display: inline-block;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail.css.patch
new file mode 100644
index 0000000..f782840
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxThumbnail.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxThumbnail.css
+===================================================================
+--- dist/modules/CdxThumbnail.css
++++ dist/modules/CdxThumbnail.css
+@@ -12,9 +12,9 @@
+ min-height: 40px;
+ width: 2.5rem;
+ height: 2.5rem;
+ border: 1px solid var(--border-color-subtle, #c8ccd1);
+- border-radius: 2px;
++ border-radius: 4px;
+ }
+ .cdx-thumbnail__image {
+ background-color: var(--background-color-base-fixed, #fff);
+ display: inline-block;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-bidi.css.patch
new file mode 100644
index 0000000..9013c53
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxToggleButton-bidi.css
+===================================================================
+--- dist/modules/CdxToggleButton-bidi.css
++++ dist/modules/CdxToggleButton-bidi.css
+@@ -17,9 +17,9 @@
+ [dir] .cdx-toggle-button {
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ transition-property: background-color, color, border-color, box-shadow;
+ transition-duration: 0.1s;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-rtl.css.patch
new file mode 100644
index 0000000..8e1a452
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxToggleButton-rtl.css
+===================================================================
+--- dist/modules/CdxToggleButton-rtl.css
++++ dist/modules/CdxToggleButton-rtl.css
+@@ -8,9 +8,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-left: 11px;
+ padding-right: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton.css.patch
new file mode 100644
index 0000000..846377c
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButton.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxToggleButton.css
+===================================================================
+--- dist/modules/CdxToggleButton.css
++++ dist/modules/CdxToggleButton.css
+@@ -8,9 +8,9 @@
+ max-width: 28rem;
+ margin: 0;
+ border-width: 1px;
+ border-style: solid;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-right: 11px;
+ padding-left: 11px;
+ font-family: inherit;
+ font-size: var(--font-size-medium, 1rem);
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-bidi.css.patch
new file mode 100644
index 0000000..628d225
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxToggleButtonGroup-bidi.css
+===================================================================
+--- dist/modules/CdxToggleButtonGroup-bidi.css
++++ dist/modules/CdxToggleButtonGroup-bidi.css
+@@ -5,9 +5,9 @@
+ width: fit-content;
+ overflow: hidden;
+ }
+ [dir] .cdx-toggle-button-group {
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ }
+ [dir='ltr'] .cdx-toggle-button-group {
+ padding-left: 1px;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-rtl.css.patch
new file mode 100644
index 0000000..d081833
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxToggleButtonGroup-rtl.css
+===================================================================
+--- dist/modules/CdxToggleButtonGroup-rtl.css
++++ dist/modules/CdxToggleButtonGroup-rtl.css
+@@ -2,9 +2,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-right: 1px;
+ overflow: hidden;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup.css.patch
new file mode 100644
index 0000000..e050d55
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxToggleButtonGroup.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxToggleButtonGroup.css
+===================================================================
+--- dist/modules/CdxToggleButtonGroup.css
++++ dist/modules/CdxToggleButtonGroup.css
+@@ -2,9 +2,9 @@
+ position: relative;
+ z-index: 0;
+ width: -webkit-fit-content;
+ width: fit-content;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding-top: 1px;
+ padding-left: 1px;
+ overflow: hidden;
+ }
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTooltip-bidi.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTooltip-bidi.css.patch
new file mode 100644
index 0000000..5c28aa0
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTooltip-bidi.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTooltip-bidi.css
+===================================================================
+--- dist/modules/CdxTooltip-bidi.css
++++ dist/modules/CdxTooltip-bidi.css
+@@ -20,9 +20,9 @@
+ line-height: var(--line-height-small, 1.375rem);
+ }
+ [dir] .cdx-tooltip {
+ background-color: var(--background-color-inverted, #101418);
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 2px 6px;
+ animation-name: cdx-animation-tooltip;
+ animation-duration: 0.1s;
+ animation-timing-function: linear;
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTooltip-rtl.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTooltip-rtl.css.patch
new file mode 100644
index 0000000..e8ac01e
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTooltip-rtl.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTooltip-rtl.css
+===================================================================
+--- dist/modules/CdxTooltip-rtl.css
++++ dist/modules/CdxTooltip-rtl.css
+@@ -6,9 +6,9 @@
+ z-index: 800;
+ width: -webkit-max-content;
+ width: max-content;
+ max-width: 16rem;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 2px 6px;
+ font-family:
+ -apple-system,
+ BlinkMacSystemFont,
diff --git a/patches/codex/wikimedia__codex__dist__modules__CdxTooltip.css.patch b/patches/codex/wikimedia__codex__dist__modules__CdxTooltip.css.patch
new file mode 100644
index 0000000..2c69bde
--- /dev/null
+++ b/patches/codex/wikimedia__codex__dist__modules__CdxTooltip.css.patch
@@ -0,0 +1,15 @@
+Index: dist/modules/CdxTooltip.css
+===================================================================
+--- dist/modules/CdxTooltip.css
++++ dist/modules/CdxTooltip.css
+@@ -6,9 +6,9 @@
+ z-index: 800;
+ width: -webkit-max-content;
+ width: max-content;
+ max-width: 16rem;
+- border-radius: 2px;
++ border-radius: 4px;
+ padding: 2px 6px;
+ font-family:
+ -apple-system,
+ BlinkMacSystemFont,
diff --git a/scripts/apply-codex-patch.mjs b/scripts/apply-codex-patch.mjs
new file mode 100644
index 0000000..1bd5c5a
--- /dev/null
+++ b/scripts/apply-codex-patch.mjs
@@ -0,0 +1,111 @@
+#!/usr/bin/env node
+// postinstall hook: re-apply the committed Codex patch (if any) on top of the
+// freshly installed published packages.
+//
+// For each patched file we re-format the installed file with the same pinned
+// Prettier config used when the patch was authored, then apply the diff. Because
+// the published artifact and the formatter are both fixed, the formatted
+// baseline is byte-identical to authoring time, so the patch always lands.
+// Content hashes (recorded in manifest.json) make this deterministic and
+// idempotent: a file that is already patched is left untouched. This runs with
+// no Codex build, so CI / PR previews stay fast.
+//
+// Authoring lives in scripts/patch-codex.mjs.
+
+import fs from 'node:fs'
+import path from 'node:path'
+
+import {
+ PATCHES_DIR,
+ formatContent,
+ installedPackageDir,
+ installedTargetPath,
+ loadJsDiff,
+ loadPrettier,
+ readManifest,
+ readPrettierOptions,
+ sha256,
+} from './lib-codex-patch.mjs'
+
+async function main() {
+ const manifest = readManifest()
+ if (!manifest || !Array.isArray(manifest.files) || manifest.files.length === 0) {
+ // No patch committed: nothing to do (e.g. a fresh template clone).
+ return
+ }
+
+ // Warn (don't fail) if the installed Codex version drifted from the one the
+ // patch was authored against; the hash checks below are the real safety net.
+ for (const [pkg, expected] of Object.entries(manifest.codexVersions ?? {})) {
+ const pkgJson = path.join(installedPackageDir(pkg), 'package.json')
+ if (fs.existsSync(pkgJson)) {
+ const actual = JSON.parse(fs.readFileSync(pkgJson, 'utf8')).version
+ if (actual !== expected) {
+ console.warn(
+ `[apply-codex-patch] ${pkg} is ${actual} but the patch was made for ${expected}. ` +
+ 'Re-run `npm run patch-codex` to regenerate if it fails to apply.',
+ )
+ }
+ }
+ }
+
+ const prettier = await loadPrettier()
+ const jsdiff = await loadJsDiff()
+ const prettierOpts = readPrettierOptions()
+
+ let applied = 0
+ let skipped = 0
+
+ for (const file of manifest.files) {
+ const patchPath = path.join(PATCHES_DIR, file.patch)
+ const targetPath = file.target ? installedTargetPath(file.target) : null
+ if (!targetPath || !fs.existsSync(targetPath)) {
+ throw new Error(`[apply-codex-patch] target not found: ${file.target}`)
+ }
+ if (!fs.existsSync(patchPath)) {
+ throw new Error(`[apply-codex-patch] patch file missing: ${file.patch}`)
+ }
+
+ // Normalize the installed file to the formatted baseline the patch expects.
+ const current = fs.readFileSync(targetPath, 'utf8')
+ const formatted = await formatContent(prettier, current, file.rel, prettierOpts)
+ const formattedSha = sha256(formatted)
+
+ if (formattedSha === file.patchedSha) {
+ // Already patched (idempotent re-run): make sure the on-disk file matches.
+ if (formatted !== current) {
+ fs.writeFileSync(targetPath, formatted)
+ }
+ skipped += 1
+ continue
+ }
+
+ if (formattedSha !== file.baseSha) {
+ throw new Error(
+ `[apply-codex-patch] ${file.target} does not match the expected published baseline. ` +
+ 'The installed Codex version or Prettier likely changed; re-run `npm run patch-codex`.',
+ )
+ }
+
+ const patchText = fs.readFileSync(patchPath, 'utf8')
+ const result = jsdiff.applyPatch(formatted, patchText)
+ if (result === false || sha256(result) !== file.patchedSha) {
+ throw new Error(
+ `[apply-codex-patch] failed to apply patch for ${file.target}. ` +
+ 'Re-run `npm run patch-codex` to regenerate.',
+ )
+ }
+
+ fs.writeFileSync(targetPath, result)
+ applied += 1
+ }
+
+ console.log(
+ `[apply-codex-patch] Codex change ${manifest.change}: applied ${applied}, already-applied ${skipped}.`,
+ )
+}
+
+main().catch((error) => {
+ console.error(error instanceof Error ? error.message : String(error))
+ process.exit(1)
+})
diff --git a/scripts/lib-codex-patch.mjs b/scripts/lib-codex-patch.mjs
new file mode 100644
index 0000000..c7f5f90
--- /dev/null
+++ b/scripts/lib-codex-patch.mjs
@@ -0,0 +1,168 @@
+// Shared helpers for the Codex Gerrit patch workflow.
+//
+// Two scripts use this:
+// - patch-codex.mjs (local "make": build the change, emit a tiny diff)
+// - apply-codex-patch.mjs (postinstall: format + apply the committed diff)
+//
+// The formatting helpers MUST behave identically in both, because the committed
+// patch is authored against `format(published)` and re-applied to
+// `format(published)` at install time. Same formatter + same input => the patch
+// always lands.
+
+import fs from 'node:fs'
+import path from 'node:path'
+import crypto from 'node:crypto'
+import { fileURLToPath } from 'node:url'
+import { spawnSync } from 'node:child_process'
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url))
+export const ROOT = path.resolve(__dirname, '..')
+export const PATCHES_DIR = path.join(ROOT, 'patches', 'codex')
+export const MANIFEST_PATH = path.join(PATCHES_DIR, 'manifest.json')
+
+export const CODEX_PACKAGES = [
+ '@wikimedia/codex',
+ '@wikimedia/codex-design-tokens',
+ '@wikimedia/codex-icons',
+]
+
+// File extensions we can format + diff as text. Anything else (images, fonts)
+// is skipped: a binary asset can't be expressed as a tiny line diff anyway.
+const FORMATTABLE_EXTS = new Set(['.css', '.scss', '.less', '.js', '.cjs', '.mjs', '.json'])
+
+const PARSER_BY_EXT = {
+ '.css': 'css',
+ '.scss': 'scss',
+ '.less': 'less',
+ '.js': 'babel',
+ '.cjs': 'babel',
+ '.mjs': 'babel',
+ '.json': 'json',
+}
+
+export function isFormattable(file) {
+ return FORMATTABLE_EXTS.has(path.extname(file))
+}
+
+export function parserForFile(file) {
+ return PARSER_BY_EXT[path.extname(file)] ?? null
+}
+
+export function run(command, args, opts = {}) {
+ const result = spawnSync(command, args, {
+ cwd: opts.cwd ?? ROOT,
+ stdio: 'pipe',
+ encoding: 'utf8',
+ maxBuffer: 1024 * 1024 * 64,
+ })
+ if (result.status !== 0) {
+ const details = [result.stdout, result.stderr].filter(Boolean).join('\n')
+ throw new Error(`Command failed: ${command} ${args.join(' ')}\n${details || '(no output)'}`)
+ }
+ return (result.stdout ?? '').trim()
+}
+
+// Raw spawn that returns status + output without throwing, for commands whose
+// non-zero exit codes are meaningful (git diff --no-index, git merge-file,
+// git apply --check).
+export function tryRun(command, args, opts = {}) {
+ const result = spawnSync(command, args, {
+ cwd: opts.cwd ?? ROOT,
+ stdio: 'pipe',
+ encoding: 'utf8',
+ maxBuffer: 1024 * 1024 * 64,
+ input: opts.input,
+ })
+ return {
+ status: result.status,
+ stdout: result.stdout ?? '',
+ stderr: result.stderr ?? '',
+ }
+}
+
+export function readPrettierOptions() {
+ const configPath = path.join(ROOT, '.prettierrc.json')
+ if (!fs.existsSync(configPath)) {
+ return {}
+ }
+ return JSON.parse(fs.readFileSync(configPath, 'utf8'))
+}
+
+export function sha256(content) {
+ return crypto.createHash('sha256').update(content).digest('hex')
+}
+
+let cachedJsDiff = null
+export async function loadJsDiff() {
+ if (cachedJsDiff) {
+ return cachedJsDiff
+ }
+ try {
+ cachedJsDiff = await import('diff')
+ } catch {
+ throw new Error(
+ 'The "diff" package is required to apply the Codex patch but is not installed. Run `npm install` with dev dependencies.',
+ )
+ }
+ return cachedJsDiff
+}
+
+let cachedPrettier = null
+export async function loadPrettier() {
+ if (cachedPrettier) {
+ return cachedPrettier
+ }
+ try {
+ cachedPrettier = await import('prettier')
+ } catch {
+ throw new Error(
+ 'Prettier is required to format Codex files but is not installed. Run `npm install` with dev dependencies.',
+ )
+ }
+ return cachedPrettier
+}
+
+export async function formatContent(prettier, content, file, baseOptions) {
+ const parser = parserForFile(file)
+ if (!parser) {
+ return content
+ }
+ const mod = prettier.default ?? prettier
+ return mod.format(content, { ...baseOptions, parser })
+}
+
+export function prettierVersion(prettier) {
+ const mod = prettier.default ?? prettier
+ return mod.version ?? 'unknown'
+}
+
+// Map a committed target (e.g. "@wikimedia/codex/dist/codex.style.css") to its
+// owning package and the path within that package.
+export function splitTarget(target) {
+ const pkg = CODEX_PACKAGES.find((name) => target === name || target.startsWith(`${name}/`))
+ if (!pkg) {
+ throw new Error(`Target is not a known Codex package file: ${target}`)
+ }
+ const rel = target.slice(pkg.length + 1)
+ return { pkg, rel }
+}
+
+export function installedPackageDir(pkg) {
+ return path.join(ROOT, 'node_modules', pkg)
+}
+
+export function installedTargetPath(target) {
+ const { pkg, rel } = splitTarget(target)
+ return path.join(installedPackageDir(pkg), rel)
+}
+
+export function patchFileName(target) {
+ return `${target.replace(/^@/, '').replace(/[/]/g, '__')}.patch`
+}
+
+export function readManifest() {
+ if (!fs.existsSync(MANIFEST_PATH)) {
+ return null
+ }
+ return JSON.parse(fs.readFileSync(MANIFEST_PATH, 'utf8'))
+}
diff --git a/scripts/patch-codex.mjs b/scripts/patch-codex.mjs
index b0a12cb..71b72f5 100644
--- a/scripts/patch-codex.mjs
+++ b/scripts/patch-codex.mjs
@@ -1,48 +1,56 @@
#!/usr/bin/env node
+// Trial an unmerged Codex Gerrit change in ProtoWiki by committing a tiny,
+// deterministic diff instead of vendored tarballs.
+//
+// npm run patch-codex -- (build + emit patch)
+// npm run patch-codex:reset (remove the patch)
+//
+// All heavy work (cloning + building Codex) happens locally. The committed
+// output is a small `patches/codex/*` diff that is re-applied at install time
+// by scripts/apply-codex-patch.mjs (wired as `postinstall`), so CI / PR
+// previews reproduce the change with no Codex build step. See
+// .agents/skills/protowiki-update-codex/references/gerrit-patch-trial.md.
+
import fs from 'node:fs'
import os from 'node:os'
import path from 'node:path'
-import { fileURLToPath } from 'node:url'
-import { spawnSync } from 'node:child_process'
-const __dirname = path.dirname(fileURLToPath(import.meta.url))
-const ROOT = path.resolve(__dirname, '..')
+import {
+ CODEX_PACKAGES,
+ MANIFEST_PATH,
+ PATCHES_DIR,
+ ROOT,
+ formatContent,
+ installedPackageDir,
+ isFormattable,
+ loadJsDiff,
+ loadPrettier,
+ patchFileName,
+ prettierVersion,
+ readManifest,
+ readPrettierOptions,
+ run,
+ sha256,
+ tryRun,
+} from './lib-codex-patch.mjs'
+
const CACHE_ROOT = path.join(os.tmpdir(), 'protowiki-codex-gerrit-cache')
const CODEX_REPO_DIR = path.join(CACHE_ROOT, 'design-codex')
-const PACK_DIR = path.join(CACHE_ROOT, 'packs')
const GERRIT_REPO = 'https://gerrit.wikimedia.org/r/design/codex'
-const CODEX_PACKAGES = [
- '@wikimedia/codex',
- '@wikimedia/codex-design-tokens',
- '@wikimedia/codex-icons',
-]
-
const PACKAGE_DIRS = new Map([
['@wikimedia/codex', 'packages/codex'],
['@wikimedia/codex-design-tokens', 'packages/codex-design-tokens'],
['@wikimedia/codex-icons', 'packages/codex-icons'],
])
-function run(command, args, opts = {}) {
- const result = spawnSync(command, args, {
- cwd: opts.cwd ?? ROOT,
- stdio: 'pipe',
- encoding: 'utf8',
- })
-
- if (result.status !== 0) {
- const details = [result.stdout, result.stderr].filter(Boolean).join('\n')
- throw new Error(`Command failed: ${command} ${args.join(' ')}\n${details || '(no output)'}`)
- }
-
- return (result.stdout ?? '').trim()
-}
-
-function readRootPackageJson() {
- const packageJsonPath = path.join(ROOT, 'package.json')
- return JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'))
-}
+// Build order matters: codex imports codex-icons' built output, so tokens and
+// icons must be built before codex.
+const CODEX_BUILD_ORDER = [
+ '@wikimedia/codex-design-tokens',
+ '@wikimedia/codex-icons',
+ '@wikimedia/codex',
+]
function parseChangeNumber(input) {
if (!input) {
@@ -52,27 +60,19 @@ function parseChangeNumber(input) {
if (/^\d+$/.test(trimmed)) {
return trimmed
}
-
const url = new URL(trimmed)
- const slashPlusMatch = url.pathname.match(/\/\+\/(\d+)(?:\/)?$/)
- if (slashPlusMatch) {
- return slashPlusMatch[1]
- }
-
- const cPathMatch = url.pathname.match(/\/c\/[^/]+\/[^/]+\/\+\/(\d+)(?:\/)?$/)
- if (cPathMatch) {
- return cPathMatch[1]
+ const match =
+ url.pathname.match(/\/\+\/(\d+)(?:\/)?$/) ||
+ url.pathname.match(/\/c\/[^/]+\/[^/]+\/\+\/(\d+)(?:\/)?$/)
+ if (match) {
+ return match[1]
}
-
throw new Error(`Could not parse Gerrit change number from: ${input}`)
}
function stripXssiPrefix(body) {
const lines = body.split('\n')
- if (lines[0] === `)]}'`) {
- return lines.slice(1).join('\n')
- }
- return body
+ return lines[0] === `)]}'` ? lines.slice(1).join('\n') : body
}
async function fetchJson(url) {
@@ -80,8 +80,34 @@ async function fetchJson(url) {
if (!response.ok) {
throw new Error(`Request failed (${response.status}): ${url}`)
}
- const text = await response.text()
- return JSON.parse(stripXssiPrefix(text))
+ return JSON.parse(stripXssiPrefix(await response.text()))
+}
+
+function computePatchRef(changeNumber, patchsetNumber) {
+ const twoDigit = changeNumber.slice(-2).padStart(2, '0')
+ return `refs/changes/${twoDigit}/${changeNumber}/${patchsetNumber}`
+}
+
+function removeInstalledCodex() {
+ for (const pkg of CODEX_PACKAGES) {
+ fs.rmSync(installedPackageDir(pkg), { recursive: true, force: true })
+ }
+}
+
+function removePatches() {
+ fs.rmSync(PATCHES_DIR, { recursive: true, force: true })
+}
+
+function freshRegistryInstall() {
+ // Patches are gone, so the postinstall applier is a no-op and node_modules
+ // ends up holding the pristine published packages (our patch baseline).
+ console.log('Installing pristine published Codex packages')
+ run('npm', ['install'], { cwd: ROOT })
+}
+
+function installedVersion(pkg) {
+ const p = path.join(installedPackageDir(pkg), 'package.json')
+ return JSON.parse(fs.readFileSync(p, 'utf8')).version
}
function ensureRepo() {
@@ -94,144 +120,258 @@ function ensureRepo() {
}
}
-function ensureDependenciesInstalled() {
- console.log('Installing Codex dependencies')
- try {
- run('npm', ['install'], { cwd: CODEX_REPO_DIR })
- } catch (error) {
- console.warn('Default npm install failed; retrying with --ignore-scripts')
+function installCodexDeps() {
+ console.log('Installing Codex build dependencies')
+ const ci = tryRun('npm', ['ci', '--ignore-scripts'], { cwd: CODEX_REPO_DIR })
+ if (ci.status !== 0) {
+ console.warn('npm ci failed; falling back to npm install --ignore-scripts')
run('npm', ['install', '--ignore-scripts'], { cwd: CODEX_REPO_DIR })
- console.warn(
- 'Installed with --ignore-scripts; this bypasses non-essential postinstall hooks in local tooling.',
- )
- if (!(error instanceof Error)) {
- throw error
- }
}
}
function buildCodex() {
- console.log('Building design/codex packages used by ProtoWiki')
- for (const packageName of CODEX_PACKAGES) {
- run('npm', ['run', 'build', '--workspace', packageName], { cwd: CODEX_REPO_DIR })
- }
-}
-
-function packOne(packageName) {
- const relDir = PACKAGE_DIRS.get(packageName)
- if (!relDir) {
- throw new Error(`No package directory mapping for ${packageName}`)
- }
- const packageDir = path.join(CODEX_REPO_DIR, relDir)
- fs.mkdirSync(PACK_DIR, { recursive: true })
- if (packageName === '@wikimedia/codex-design-tokens') {
- const distDir = path.join(packageDir, 'dist')
- for (const entry of fs.readdirSync(distDir)) {
- if (!entry.startsWith('theme-')) continue
- const src = path.join(distDir, entry)
- const dest = path.join(packageDir, entry)
- fs.copyFileSync(src, dest)
- }
- }
- const packOutput = run('npm', ['pack', '--pack-destination', PACK_DIR], { cwd: packageDir })
- const tarballName = packOutput
- .split('\n')
- .map((line) => line.trim())
- .filter((line) => line.endsWith('.tgz'))
- .at(-1)
- if (!tarballName) {
- throw new Error(`Unable to find tarball name in npm pack output for ${packageName}`)
+ for (const pkg of CODEX_BUILD_ORDER) {
+ run('npm', ['run', 'build', '--workspace', pkg], { cwd: CODEX_REPO_DIR })
}
- return path.join(PACK_DIR, tarballName)
}
-function installTarballs(tarballs) {
- console.log('Installing local Codex tarballs into ProtoWiki')
- run('npm', ['install', '--no-save', ...tarballs], { cwd: ROOT })
-}
+// Collect every distributed build artifact of the installed Codex packages:
+// everything under `dist/**` plus the root `theme-*` token files. Patching the
+// full distribution (rather than only the files ProtoWiki imports today) keeps
+// the patched node_modules self-contained — any current or future import sees
+// the change, with no need to re-run when ProtoWiki's imports change. Package
+// metadata (package.json / README / LICENSE) is deliberately excluded.
+function collectCandidates() {
+ const candidates = []
+
+ for (const pkg of CODEX_PACKAGES) {
+ const pkgRoot = installedPackageDir(pkg)
+ if (!fs.existsSync(pkgRoot)) continue
+ const buildPkgDir = path.join(CODEX_REPO_DIR, PACKAGE_DIRS.get(pkg))
+
+ const add = (rel) => {
+ const installedFile = path.join(pkgRoot, rel)
+ if (!isFormattable(installedFile)) return
+ // Token theme files ship at the package root but are built into dist/.
+ const buildRel =
+ !rel.includes('/') && rel.startsWith('theme-') ? path.join('dist', rel) : rel
+ candidates.push({
+ target: `${pkg}/${rel}`,
+ pkg,
+ rel,
+ installedFile,
+ buildFile: path.join(buildPkgDir, buildRel),
+ })
+ }
-function resetToRegistry() {
- const packageJson = readRootPackageJson()
- const desired = CODEX_PACKAGES.map((name) => {
- const range = packageJson.dependencies?.[name]
- if (!range) {
- throw new Error(`Missing dependency range in package.json: ${name}`)
+ const distDir = path.join(pkgRoot, 'dist')
+ if (fs.existsSync(distDir)) {
+ const walk = (dir) => {
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
+ const abs = path.join(dir, entry.name)
+ if (entry.isDirectory()) {
+ walk(abs)
+ } else if (entry.isFile()) {
+ add(path.relative(pkgRoot, abs))
+ }
+ }
+ }
+ walk(distDir)
}
- return `${name}@${range}`
- })
- console.log('Restoring Codex packages from npm registry')
- run('npm', ['install', '--no-save', ...desired], { cwd: ROOT })
- printInstalledVersions('reset complete')
+ for (const entry of fs.readdirSync(pkgRoot, { withFileTypes: true })) {
+ if (entry.isFile() && entry.name.startsWith('theme-')) {
+ add(entry.name)
+ }
+ }
+ }
+
+ return candidates
}
-function printInstalledVersions(context) {
- const lines = CODEX_PACKAGES.map((name) => {
- const p = path.join(ROOT, 'node_modules', name, 'package.json')
- if (!fs.existsSync(p)) {
- return `${name}: not installed`
+function snapshotBuild(candidates) {
+ const map = new Map()
+ for (const candidate of candidates) {
+ if (fs.existsSync(candidate.buildFile)) {
+ map.set(candidate.target, fs.readFileSync(candidate.buildFile, 'utf8'))
}
- const v = JSON.parse(fs.readFileSync(p, 'utf8')).version
- return `${name}: ${v}`
- })
- console.log(`\nCodex package state (${context}):`)
- for (const line of lines) {
- console.log(`- ${line}`)
}
+ return map
}
-function computePatchRef(changeNumber, patchsetNumber) {
- const twoDigit = changeNumber.slice(-2).padStart(2, '0')
- return `refs/changes/${twoDigit}/${changeNumber}/${patchsetNumber}`
+// 3-way merge: layer the base->other change onto current, returning the merged
+// text. Throws on conflict so we never emit a wrong patch silently.
+function mergeThreeWay(rel, current, base, other) {
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'codex-merge-'))
+ try {
+ const cur = path.join(tmp, 'current')
+ const bse = path.join(tmp, 'base')
+ const oth = path.join(tmp, 'other')
+ fs.writeFileSync(cur, current)
+ fs.writeFileSync(bse, base)
+ fs.writeFileSync(oth, other)
+ const res = tryRun('git', ['merge-file', '-p', cur, bse, oth])
+ if (res.status === 0) return res.stdout
+ if (res.status > 0) {
+ throw new Error(
+ `Could not cleanly merge the change into the published ${rel} ` +
+ '(the change overlaps with version differences). Try a newer patchset or re-run.',
+ )
+ }
+ throw new Error(`git merge-file failed for ${rel}: ${res.stderr}`)
+ } finally {
+ fs.rmSync(tmp, { recursive: true, force: true })
+ }
}
-async function applyChange(changeInput) {
+async function makePatch(changeInput) {
const changeNumber = parseChangeNumber(changeInput)
console.log(`Fetching Gerrit change ${changeNumber}`)
-
const detailUrl = `https://gerrit.wikimedia.org/r/changes/design%2Fcodex~${changeNumber}/detail`
const detail = await fetchJson(detailUrl)
-
const patchset = detail.current_revision_number
if (!patchset) {
throw new Error('Gerrit response did not include current_revision_number')
}
-
const ref = computePatchRef(changeNumber, String(patchset))
console.log(`Current patchset: ${patchset} (${ref})`)
+ // 1. Establish a pristine published baseline in node_modules.
+ removePatches()
+ removeInstalledCodex()
+ freshRegistryInstall()
+ const versions = Object.fromEntries(CODEX_PACKAGES.map((pkg) => [pkg, installedVersion(pkg)]))
+ console.log(`Baseline Codex version: ${versions['@wikimedia/codex']}`)
+
+ // 2. Fetch the change and its parent.
ensureRepo()
run('git', ['fetch', 'origin', ref], { cwd: CODEX_REPO_DIR })
- run('git', ['checkout', '--detach', 'FETCH_HEAD'], { cwd: CODEX_REPO_DIR })
+ const changeSha = run('git', ['rev-parse', 'FETCH_HEAD'], { cwd: CODEX_REPO_DIR })
+ const parentSha = run('git', ['rev-parse', 'FETCH_HEAD^'], { cwd: CODEX_REPO_DIR })
+
+ // 3. Build the unpatched parent and the patched change.
+ const candidates = collectCandidates()
- ensureDependenciesInstalled()
+ console.log('Building Codex at the parent commit (unpatched)')
+ run('git', ['checkout', '--force', '--detach', parentSha], { cwd: CODEX_REPO_DIR })
+ installCodexDeps()
buildCodex()
+ const unpatched = snapshotBuild(candidates)
- const tarballs = CODEX_PACKAGES.map(packOne)
- installTarballs(tarballs)
+ console.log('Building Codex at the change commit (patched)')
+ run('git', ['checkout', '--force', '--detach', changeSha], { cwd: CODEX_REPO_DIR })
+ buildCodex()
+ const patched = snapshotBuild(candidates)
+
+ // 4. The change's true footprint: files that differ between the two builds.
+ const changed = candidates.filter((candidate) => {
+ const before = unpatched.get(candidate.target)
+ const after = patched.get(candidate.target)
+ return before !== undefined && after !== undefined && before !== after
+ })
- const revision = detail.current_revision ?? '(unknown revision)'
- printInstalledVersions(`patched from Gerrit ${changeNumber}`)
- console.log(`\nApplied change ${changeNumber} at revision ${revision}.`)
- console.log('To restore registry versions: npm run patch-codex:reset')
+ if (changed.length === 0) {
+ throw new Error('The change produced no differences in any distributed Codex file.')
+ }
+
+ // 5. For each changed file, layer the change onto the published file (after
+ // formatting, so the minified single-line CSS diffs by content not file size).
+ const prettier = await loadPrettier()
+ const jsdiff = await loadJsDiff()
+ const prettierOpts = readPrettierOptions()
+
+ fs.mkdirSync(PATCHES_DIR, { recursive: true })
+ const manifestFiles = []
+
+ for (const candidate of changed) {
+ const published = fs.readFileSync(candidate.installedFile, 'utf8')
+ const fmtPublished = await formatContent(prettier, published, candidate.rel, prettierOpts)
+ const fmtBase = await formatContent(
+ prettier,
+ unpatched.get(candidate.target),
+ candidate.rel,
+ prettierOpts,
+ )
+ const fmtOther = await formatContent(
+ prettier,
+ patched.get(candidate.target),
+ candidate.rel,
+ prettierOpts,
+ )
+
+ const merged = mergeThreeWay(candidate.rel, fmtPublished, fmtBase, fmtOther)
+ const fmtMerged = await formatContent(prettier, merged, candidate.rel, prettierOpts)
+ if (fmtMerged === fmtPublished) continue
+
+ const diff = jsdiff.createPatch(candidate.rel, fmtPublished, fmtMerged, '', '')
+ const fileName = patchFileName(candidate.target)
+ fs.writeFileSync(path.join(PATCHES_DIR, fileName), diff)
+ manifestFiles.push({
+ target: candidate.target,
+ package: candidate.pkg,
+ rel: candidate.rel,
+ patch: fileName,
+ // Content hashes of the formatted baseline and patched result, so the
+ // applier can apply (and skip re-applying) deterministically.
+ baseSha: sha256(fmtPublished),
+ patchedSha: sha256(fmtMerged),
+ })
+ console.log(` patched ${candidate.target}`)
+ }
+
+ if (manifestFiles.length === 0) {
+ throw new Error('No effective changes remained after merging into the published files.')
+ }
+
+ const manifest = {
+ change: changeNumber,
+ patchset,
+ revision: changeSha,
+ generatedAt: new Date().toISOString(),
+ codexVersions: versions,
+ prettierVersion: prettierVersion(prettier),
+ files: manifestFiles,
+ }
+ fs.writeFileSync(MANIFEST_PATH, `${JSON.stringify(manifest, null, 2)}\n`)
+
+ // 6. Apply locally via the same path CI uses.
+ console.log('Applying patch locally')
+ console.log(run('node', ['scripts/apply-codex-patch.mjs'], { cwd: ROOT }))
+
+ console.log(`\nWrote ${manifestFiles.length} patch file(s) to patches/codex/.`)
+ console.log('Commit patches/codex/ so the PR preview reproduces the change.')
+ console.log('To remove the patch: npm run patch-codex:reset')
+}
+
+function reset() {
+ const manifest = readManifest()
+ removePatches()
+ removeInstalledCodex()
+ freshRegistryInstall()
+ if (manifest) {
+ console.log(`Removed Codex patch for change ${manifest.change}; restored published packages.`)
+ } else {
+ console.log('No Codex patch present; reinstalled published packages.')
+ }
+}
+
+function printUsage() {
+ console.log('Usage:')
+ console.log(' npm run patch-codex -- ')
+ console.log(' npm run patch-codex:reset')
}
async function main() {
- const [, , firstArg, secondArg] = process.argv
+ const [, , firstArg] = process.argv
if (firstArg === '--reset') {
- resetToRegistry()
+ reset()
return
}
-
- const changeInput = secondArg && firstArg === '--change' ? secondArg : firstArg
- if (!changeInput) {
- console.log('Usage:')
- console.log(' npm run patch-codex -- ')
- console.log(' npm run patch-codex:reset')
+ if (!firstArg) {
+ printUsage()
process.exit(1)
}
-
- await applyChange(changeInput)
+ await makePatch(firstArg)
}
main().catch((error) => {
diff --git a/src/prototypes/example-codex-kitchen-sink/index.vue b/src/prototypes/example-codex-kitchen-sink/index.vue
index d3cc2ed..d2ac379 100644
--- a/src/prototypes/example-codex-kitchen-sink/index.vue
+++ b/src/prototypes/example-codex-kitchen-sink/index.vue
@@ -15,8 +15,8 @@ import SpecialPageWrapper from '@/components/SpecialPageWrapper.vue'
definePage({
meta: {
- title: 'Example: Codex kitchen sink',
- description: 'Quick visual regression surface for border radius and grouped controls.',
+ title: 'Codex playground',
+ description: 'An assortment of Codex components.',
},
})
@@ -55,7 +55,7 @@ const primaryAction = computed(() => ({
-
+
Inputs and compact controls
@@ -101,9 +101,7 @@ const primaryAction = computed(() => ({
Toggle disabled third button
-
- Open Dialog
-
+ Open Dialog
Selected filter: {{ selectedFilter }}
@@ -122,9 +120,7 @@ const primaryAction = computed(() => ({
-
- Cancel
-
+ Cancel
Add item
@@ -151,7 +147,10 @@ const primaryAction = computed(() => ({
.kitchen-sink__title {
margin: 0;
+ font-family: var(--font-family-base);
font-size: var(--font-size-large);
+ font-weight: var(--font-weight-bold);
+ line-height: var(--line-height-large);
}
.kitchen-sink__description {
From 0a4b91f372e106008917069aecf8fc8b6c70bd09 Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Wed, 17 Jun 2026 13:45:33 +0100
Subject: [PATCH 03/10] test commit
---
src/prototypes/example-codex-kitchen-sink/index.vue | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/prototypes/example-codex-kitchen-sink/index.vue b/src/prototypes/example-codex-kitchen-sink/index.vue
index d2ac379..a008e1b 100644
--- a/src/prototypes/example-codex-kitchen-sink/index.vue
+++ b/src/prototypes/example-codex-kitchen-sink/index.vue
@@ -59,9 +59,6 @@ const primaryAction = computed(() => ({
Inputs and compact controls
-
- Use this block to compare rounded corners across input fields and buttons.
-
Search article
From e88c174758fc03eedbabb2a41cb57c5e1baa5173 Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Wed, 17 Jun 2026 15:39:57 +0100
Subject: [PATCH 04/10] commit for now (more cleanup to do)
---
.../example-codex-kitchen-sink/index.vue | 204 ++++---------
.../lib/fixtures.ts | 67 +++++
.../lib/parse-tokens.ts | 234 +++++++++++++++
.../playground/PlaygroundCell.vue | 51 ++++
.../playground/PlaygroundGrid.vue | 35 +++
.../playground/PlaygroundSection.vue | 35 +++
.../playground/TokenSwatch.vue | 203 +++++++++++++
.../sections/ButtonsSection.vue | 222 ++++++++++++++
.../sections/FeedbackSection.vue | 82 +++++
.../sections/IconsSection.vue | 37 +++
.../sections/InputsSection.vue | 283 ++++++++++++++++++
.../sections/LayoutSection.vue | 136 +++++++++
.../sections/OverlaysSection.vue | 184 ++++++++++++
.../sections/TokensSection.vue | 66 ++++
.../sections/TypographySection.vue | 37 +++
15 files changed, 1723 insertions(+), 153 deletions(-)
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PlaygroundGrid.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
diff --git a/src/prototypes/example-codex-kitchen-sink/index.vue b/src/prototypes/example-codex-kitchen-sink/index.vue
index a008e1b..1f0d16d 100644
--- a/src/prototypes/example-codex-kitchen-sink/index.vue
+++ b/src/prototypes/example-codex-kitchen-sink/index.vue
@@ -1,177 +1,75 @@
-
-
-
-
- Inputs and compact controls
-
-
- Search article
-
-
-
-
- Project
-
-
-
-
- Selected action: {{ selectedAction }}
-
-
-
- Long one-line button-group labels
-
- This row intentionally stresses first/last button edge rounding.
-
-
-
+
+
-
-
- Toggle disabled third button
-
- Open Dialog
-
- Selected filter: {{ selectedFilter }}
+
+
+
+
-
+
+
-
-
- Compare button radius in default, quiet, and progressive states while this dialog is open.
-
-
-
- Cancel
-
- Add item
-
-
-
-
-
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts b/src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts
new file mode 100644
index 0000000..e26ecca
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts
@@ -0,0 +1,67 @@
+import { cdxIconAdd, cdxIconEdit, cdxIconTrash } from '@wikimedia/codex-icons'
+import type { ChipInputItem, MenuItemData, SearchResult, TableColumn, TableRow } from '@wikimedia/codex'
+
+export const menuItems: MenuItemData[] = [
+ { label: 'Edit', value: 'edit', icon: cdxIconEdit },
+ { label: 'Delete', value: 'delete', icon: cdxIconTrash },
+ { label: 'Add', value: 'add', icon: cdxIconAdd },
+]
+
+export const selectOptions = [
+ { label: 'Option A', value: 'a' },
+ { label: 'Option B', value: 'b' },
+ { label: 'Option C', value: 'c' },
+]
+
+export const lookupResults: MenuItemData[] = [
+ { label: 'Albert Einstein', value: 'Albert Einstein' },
+ { label: 'Albert Camus', value: 'Albert Camus' },
+ { label: 'Alberta', value: 'Alberta' },
+]
+
+export const chipItems: ChipInputItem[] = [
+ { value: 'alpha' },
+ { value: 'beta' },
+]
+
+export const tableColumns: TableColumn[] = [
+ { id: 'title', label: 'Title', sortable: true },
+ { id: 'status', label: 'Status', sortable: true },
+ { id: 'views', label: 'Views', sortable: true },
+]
+
+export const tableRows: TableRow[] = [
+ { id: '1', title: 'Mont Blanc', status: 'Published', views: 1200 },
+ { id: '2', title: 'Lake Geneva', status: 'Draft', views: 340 },
+ { id: '3', title: 'Rhine', status: 'Published', views: 890 },
+]
+
+export const searchResults: SearchResult[] = [
+ { label: 'Albert Einstein', title: 'Albert Einstein', description: 'German-born theoretical physicist' },
+ { label: 'Albert Camus', title: 'Albert Camus', description: 'French philosopher and author' },
+]
+
+const wetLegImageUrl = `${import.meta.env.BASE_URL}images/wet-leg-o2-infobox.jpg`
+
+export const thumbnailUrl = wetLegImageUrl
+
+export const imageUrl = wetLegImageUrl
+
+export const buttonGroupItems = [
+ { value: 'edit', label: 'Edit' },
+ { value: 'history', label: 'History' },
+ { value: 'watch', label: 'Watch' },
+]
+
+export const buttonGroupLongItems = [
+ { value: 'all', label: 'All' },
+ { value: 'newcomers', label: 'Newcomers' },
+ { value: 'mobile', label: 'Mobile edits' },
+ { value: 'needs-review', label: 'Needs review' },
+]
+
+export const toggleGroupItems = [
+ { value: 'left', label: 'Left' },
+ { value: 'center', label: 'Center' },
+ { value: 'right', label: 'Right' },
+]
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
new file mode 100644
index 0000000..31e1e31
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
@@ -0,0 +1,234 @@
+export type TokenKind =
+ | 'color-text'
+ | 'color-bg'
+ | 'color-border'
+ | 'spacing'
+ | 'radius'
+ | 'font-size'
+ | 'font-weight'
+ | 'font-family'
+ | 'line-height'
+ | 'shadow'
+ | 'opacity'
+ | 'size'
+ | 'generic'
+
+export type TokenFamily =
+ | 'Color'
+ | 'Spacing'
+ | 'Typography'
+ | 'Border'
+ | 'Shadow'
+ | 'Sizing'
+ | 'Opacity'
+ | 'Transition'
+ | 'Z-index'
+ | 'Other'
+
+export interface TokenEntry {
+ name: string
+ value: string
+ category: string
+ kind: TokenKind
+ family: TokenFamily
+}
+
+export interface TokenFamilyGroup {
+ family: TokenFamily
+ categories: { category: string; tokens: TokenEntry[] }[]
+}
+
+export function inferTokenKind(name: string): TokenKind {
+ if (name.startsWith('--background-color-')) return 'color-bg'
+ if (name.startsWith('--border-color-')) return 'color-border'
+ if (name.startsWith('--color-')) return 'color-text'
+ if (name.startsWith('--spacing-')) return 'spacing'
+ if (name.startsWith('--border-radius-')) return 'radius'
+ if (name.startsWith('--font-size-')) return 'font-size'
+ if (name.startsWith('--font-weight-')) return 'font-weight'
+ if (name.startsWith('--font-family-')) return 'font-family'
+ if (name.startsWith('--line-height-')) return 'line-height'
+ if (name.startsWith('--box-shadow-')) return 'shadow'
+ if (name.startsWith('--opacity-')) return 'opacity'
+ if (
+ name.startsWith('--size-') ||
+ name.startsWith('--min-size-') ||
+ name.startsWith('--max-size-') ||
+ name.startsWith('--min-width-') ||
+ name.startsWith('--max-width-') ||
+ name.startsWith('--width-') ||
+ name.startsWith('--height-')
+ ) {
+ return 'size'
+ }
+ return 'generic'
+}
+
+export function inferTokenFamily(name: string): TokenFamily {
+ if (
+ name.startsWith('--color-') ||
+ name.startsWith('--background-color-') ||
+ name.startsWith('--border-color-') ||
+ name.startsWith('--accent-color-')
+ ) {
+ return 'Color'
+ }
+ if (name.startsWith('--spacing-')) return 'Spacing'
+ if (name.startsWith('--font-') || name.startsWith('--line-height-') || name.startsWith('--letter-spacing-')) {
+ return 'Typography'
+ }
+ if (name.startsWith('--border-radius-') || name.startsWith('--border-width-') || name.startsWith('--border-style-')) {
+ return 'Border'
+ }
+ if (name.startsWith('--box-shadow-')) return 'Shadow'
+ if (name.startsWith('--opacity-')) return 'Opacity'
+ if (name.startsWith('--transition-') || name.startsWith('--animation-')) return 'Transition'
+ if (name.startsWith('--z-index-')) return 'Z-index'
+ if (
+ name.startsWith('--size-') ||
+ name.startsWith('--min-') ||
+ name.startsWith('--max-') ||
+ name.startsWith('--width-') ||
+ name.startsWith('--height-')
+ ) {
+ return 'Sizing'
+ }
+ return 'Other'
+}
+
+export function inferTokenCategory(name: string): string {
+ if (name.startsWith('--background-color-')) return 'Background colors'
+ if (name.startsWith('--border-color-')) return 'Border colors'
+ if (name.startsWith('--color-')) return 'Text colors'
+ if (name.startsWith('--spacing-')) return 'Spacing'
+ if (name.startsWith('--border-radius-') || name.startsWith('--border-width-') || name.startsWith('--border-style-')) {
+ return 'Border'
+ }
+ if (name.startsWith('--font-') || name.startsWith('--line-height-') || name.startsWith('--letter-spacing-')) {
+ return 'Typography'
+ }
+ if (name.startsWith('--box-shadow-')) return 'Shadow'
+ if (name.startsWith('--opacity-')) return 'Opacity'
+ if (name.startsWith('--size-') || name.startsWith('--min-') || name.startsWith('--max-') || name.startsWith('--width-') || name.startsWith('--height-')) {
+ return 'Sizing'
+ }
+ if (name.startsWith('--transition-')) return 'Transition'
+ if (name.startsWith('--animation-')) return 'Animation'
+ if (name.startsWith('--z-index-')) return 'Z-index'
+ if (name.startsWith('--cursor-')) return 'Cursor'
+ if (name.startsWith('--filter-')) return 'Filter'
+ if (name.startsWith('--outline-')) return 'Outline'
+ if (name.startsWith('--mix-blend-mode-')) return 'Blend mode'
+ if (name.startsWith('--text-decoration-')) return 'Text decoration'
+ if (name.startsWith('--transform-')) return 'Transform'
+ if (name.startsWith('--accent-color-')) return 'Accent'
+ if (name.startsWith('--position-')) return 'Position'
+ if (name.startsWith('--tab-size-')) return 'Tab size'
+ return 'Other'
+}
+
+export function parseTokensFromCss(css: string): TokenEntry[] {
+ const rootMatch = css.match(/:root\s*\{([\s\S]*?)\n\}/)
+ if (!rootMatch) return []
+
+ const seen = new Set()
+ const tokens: TokenEntry[] = []
+ const re = /^\s*(--[a-z0-9-]+):\s*([^;]+);/gm
+ let match: RegExpExecArray | null
+
+ while ((match = re.exec(rootMatch[1])) !== null) {
+ const name = match[1]
+ if (seen.has(name)) continue
+ seen.add(name)
+ tokens.push({
+ name,
+ value: match[2].trim(),
+ category: inferTokenCategory(name),
+ kind: inferTokenKind(name),
+ family: inferTokenFamily(name),
+ })
+ }
+
+ return tokens.sort((a, b) => a.name.localeCompare(b.name))
+}
+
+const categoryOrder = [
+ 'Text colors',
+ 'Background colors',
+ 'Border colors',
+ 'Spacing',
+ 'Border',
+ 'Typography',
+ 'Shadow',
+ 'Opacity',
+ 'Sizing',
+ 'Transition',
+ 'Animation',
+ 'Z-index',
+ 'Cursor',
+ 'Filter',
+ 'Outline',
+ 'Blend mode',
+ 'Text decoration',
+ 'Transform',
+ 'Accent',
+ 'Position',
+ 'Tab size',
+ 'Other',
+] as const
+
+const familyOrder: TokenFamily[] = [
+ 'Color',
+ 'Spacing',
+ 'Typography',
+ 'Border',
+ 'Shadow',
+ 'Sizing',
+ 'Opacity',
+ 'Transition',
+ 'Z-index',
+ 'Other',
+]
+
+function groupByCategory(tokens: TokenEntry[]): { category: string; tokens: TokenEntry[] }[] {
+ const map = new Map()
+ for (const token of tokens) {
+ const list = map.get(token.category) ?? []
+ list.push(token)
+ map.set(token.category, list)
+ }
+
+ return categoryOrder
+ .filter((category) => map.has(category))
+ .map((category) => ({ category, tokens: map.get(category)! }))
+}
+
+export function groupTokensByCategory(tokens: TokenEntry[]): { category: string; tokens: TokenEntry[] }[] {
+ return groupByCategory(tokens)
+}
+
+export function groupTokensByFamily(
+ tokens: TokenEntry[],
+ options: { exclude?: TokenFamily[] } = {},
+): TokenFamilyGroup[] {
+ const excluded = new Set(options.exclude ?? [])
+ const byFamily = new Map()
+
+ for (const token of tokens) {
+ if (excluded.has(token.family)) continue
+ const list = byFamily.get(token.family) ?? []
+ list.push(token)
+ byFamily.set(token.family, list)
+ }
+
+ return familyOrder
+ .filter((family) => !excluded.has(family) && byFamily.has(family))
+ .map((family) => ({
+ family,
+ categories: groupByCategory(byFamily.get(family)!),
+ }))
+}
+
+export function getTokensForFamily(tokens: TokenEntry[], family: TokenFamily): TokenEntry[] {
+ return tokens.filter((token) => token.family === family)
+}
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue
new file mode 100644
index 0000000..85ecc6d
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundGrid.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundGrid.vue
new file mode 100644
index 0000000..0ff6e42
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundGrid.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
new file mode 100644
index 0000000..26e77db
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue b/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
new file mode 100644
index 0000000..3a86787
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+ Aa
+
+
+ Sample
+
+
+ Aa
+
+
+ Line one
Line two
+
+
+
+
+ {{ token.value }}
+
+
+
{{ token.name }}
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue
new file mode 100644
index 0000000..e538dd0
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue
@@ -0,0 +1,222 @@
+
+
+
+
+
+
+
+ Label
+
+
+
+
+ Label
+
+
+
+
+
+ Add
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Off
+
+
+ On
+
+
+ Disabled
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Status
+
+
+ Status
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue
new file mode 100644
index 0000000..6e697e8
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+ Message text
+
+
+ Message text
+
+
+
+ Message text
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading
+
+
+
+
+
+
+
+
+ Show
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
new file mode 100644
index 0000000..574b693
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue
new file mode 100644
index 0000000..07c4f0d
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue
@@ -0,0 +1,283 @@
+
+
+
+
+
+
+
+ Label
+ Description
+
+ Help text
+
+
+
+
+
+
+
+
+ Label text
+
+
+ Optional label
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Checkbox
+
+
+ Checked
+
+
+ Disabled
+
+
+ A
+ B
+ C
+
+
+
+
+
+
+
+ Published
+ Draft
+
+
+ Draft
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Title
+ Description
+ Body content
+
+
+
+
+ Title
+ Body content
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue
new file mode 100644
index 0000000..2e7c517
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+ Title
+ Description
+
+
+
+
+ Title
+ Description
+
+
+
+
+ Title
+
+
+
+
+ Title
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Article
+
+
+ Talk
+
+
+ History
+
+
+
+
+
+
+ Article
+
+
+ Talk
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue
new file mode 100644
index 0000000..ae65791
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+ Open
+
+
+ Open
+
+
+ Open
+
+
+ Open
+
+
+
+
+ Body
+
+
+
+ Body
+
+
+
+ Body
+
+ Cancel
+
+ Add
+
+
+
+
+
+ Body
+
+
+
+
+
+
+ Hover
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Pending
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Toggle
+
+ Body
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
new file mode 100644
index 0000000..c859ecb
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
@@ -0,0 +1,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
new file mode 100644
index 0000000..3c984d3
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
@@ -0,0 +1,37 @@
+
+
+
+
+ Heading 1
+ Heading 2
+ Heading 3
+ Heading 4
+ Body
+ Small
+ Cite
+ Figure caption
+ Code
+ Block quote
+ Pre
+
+
+
+
+
+
+
+
+
+
From 472a2e5af63d9f1878c3f06499e1b8749ac6a452 Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Mon, 22 Jun 2026 12:18:31 +0100
Subject: [PATCH 05/10] bump lockfile
---
package-lock.json | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/package-lock.json b/package-lock.json
index 3283816..2036a5e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1297,6 +1297,7 @@
"integrity": "sha512-wGdMcf+vPYM6jikpS/qhg6WiqSV/OhG+jeeHT/KlVqxYfD40iYJf9/AE1uQxVWFvU7MipKRkRv8NSHiCGgPr8Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"undici-types": "~6.21.0"
}
@@ -1346,6 +1347,7 @@
"integrity": "sha512-HDQH9O/47Dxi1ceDhBXdaldtf/WV9yRYMjbjCuNk3qnaTD564qwv61Y7+gTxwxRKzSrgO5uhtw584igXVuuZkA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.59.1",
"@typescript-eslint/types": "8.59.1",
@@ -1917,6 +1919,7 @@
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2337,6 +2340,7 @@
"integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -2397,6 +2401,7 @@
"integrity": "sha512-EFNNzu4HqtTRb5DJINpyd+u3bDdzETWDMpCzG+UBHz1tpsnMDCeOcf61u4Wy/cbXnMymK+MT9bjH7KcG1fItSw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"natural-compare": "^1.4.0",
@@ -3275,6 +3280,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -3422,6 +3428,7 @@
"integrity": "sha512-J9qZyW++QK/09NyN/zeO0dG/1GdGfyp9lV8ajHnRVLfo/uFsbji5mHnDgn/qYdUHyCkM2N+8VyspgZclfAh0eQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@types/estree": "1.0.8"
},
@@ -3604,6 +3611,7 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -3656,6 +3664,7 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
+ "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3771,6 +3780,7 @@
"integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
@@ -3864,6 +3874,7 @@
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -3883,6 +3894,7 @@
"resolved": "https://registry.npmjs.org/vue/-/vue-3.5.33.tgz",
"integrity": "sha512-1AgChhx5w3ALgT4oK3acm2Es/7jyZhWSVUfs3rOBlGQC0rjEDkS7G4lWlJJGGNQD+BV3reCwbQrOe1mPNwKHBQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/compiler-dom": "3.5.33",
"@vue/compiler-sfc": "3.5.33",
@@ -3941,6 +3953,7 @@
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
"integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"@vue/devtools-api": "^6.6.4"
},
From 9b0d9b1fb00aec20c4ecd695df9cca028b6f67ce Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Mon, 22 Jun 2026 12:43:35 +0100
Subject: [PATCH 06/10] move around
---
.../example-codex-kitchen-sink/index.vue | 44 ++++++++++--
.../lib/parse-tokens.ts | 4 ++
.../lib/use-url-query-param.ts | 67 +++++++++++++++++++
.../playground/PlaygroundSection.vue | 10 ++-
.../playground/PlaygroundTab.vue | 15 +++++
.../sections/TokensSection.vue | 21 ++++--
.../sections/TypographySection.vue | 43 ++++++------
7 files changed, 169 insertions(+), 35 deletions(-)
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PlaygroundTab.vue
diff --git a/src/prototypes/example-codex-kitchen-sink/index.vue b/src/prototypes/example-codex-kitchen-sink/index.vue
index 1f0d16d..4beea41 100644
--- a/src/prototypes/example-codex-kitchen-sink/index.vue
+++ b/src/prototypes/example-codex-kitchen-sink/index.vue
@@ -1,5 +1,6 @@
@@ -61,15 +87,23 @@ const activeTab = ref<(typeof navItems)[number]['id']>('typography')
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
index 31e1e31..61fea8a 100644
--- a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
+++ b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
@@ -232,3 +232,7 @@ export function groupTokensByFamily(
export function getTokensForFamily(tokens: TokenEntry[], family: TokenFamily): TokenEntry[] {
return tokens.filter((token) => token.family === family)
}
+
+export function isTokenFamily(value: string): value is TokenFamily {
+ return familyOrder.includes(value as TokenFamily)
+}
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts b/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
new file mode 100644
index 0000000..bd9ac78
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
@@ -0,0 +1,67 @@
+import { ref, watch, type Ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+
+function readQueryValue(raw: unknown): string | undefined {
+ if (typeof raw === 'string') return raw
+ if (Array.isArray(raw) && typeof raw[0] === 'string') return raw[0]
+ return undefined
+}
+
+export { readQueryValue }
+
+export function useUrlQueryParam(
+ key: string,
+ defaultValue: T,
+ isValid: (value: string) => value is T,
+): Ref {
+ const route = useRoute()
+ const router = useRouter()
+
+ function parse(): T {
+ const value = readQueryValue(route.query[key])
+ if (value && isValid(value)) return value
+ return defaultValue
+ }
+
+ const param = ref(parse()) as Ref
+ let syncingFromRoute = false
+
+ watch(
+ () => route.query[key],
+ () => {
+ const next = parse()
+ if (next === param.value) return
+ syncingFromRoute = true
+ param.value = next
+ syncingFromRoute = false
+ },
+ )
+
+ watch(param, (value) => {
+ if (syncingFromRoute) return
+
+ const normalized = value === defaultValue ? undefined : value
+ const current = readQueryValue(route.query[key])
+ if (current === normalized) return
+
+ const query = { ...route.query }
+ if (normalized === undefined) {
+ delete query[key]
+ } else {
+ query[key] = normalized
+ }
+ void router.replace({ query })
+ })
+
+ return param
+}
+
+export function removeUrlQueryParam(key: string): void {
+ const route = useRoute()
+ const router = useRouter()
+ if (!(key in route.query)) return
+
+ const query = { ...route.query }
+ delete query[key]
+ void router.replace({ query })
+}
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
index 26e77db..6fc75eb 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
@@ -1,7 +1,7 @@
-
-
+
+
{{ title }}
@@ -25,6 +25,10 @@ withDefaults(defineProps(), {
/* gap: var(--spacing-75); */
}
+.playground-section--untitled {
+ padding-top: var(--spacing-100);
+}
+
/* .playground-section__title {
font-family: var(--font-family-base);
font-size: var(--font-size-medium);
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundTab.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundTab.vue
new file mode 100644
index 0000000..7c9f21d
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundTab.vue
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
index c859ecb..b09596d 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
@@ -1,15 +1,16 @@
@@ -49,18 +50,24 @@ const activeFamily = ref(families[0]?.family ?? 'Color')
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
index 3c984d3..6027625 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
@@ -4,34 +4,37 @@ import { getTokensForFamily, parseTokensFromCss } from '../lib/parse-tokens'
import PlaygroundSection from '../playground/PlaygroundSection.vue'
import PlaygroundGrid from '../playground/PlaygroundGrid.vue'
import TokenSwatch from '../playground/TokenSwatch.vue'
+import PlaygroundTab from '../playground/PlaygroundTab.vue'
const typographyTokens = getTokensForFamily(parseTokensFromCss(tokensCss), 'Typography')
-
- Heading 1
- Heading 2
- Heading 3
- Heading 4
- Body
- Small
- Cite
- Figure caption
- Code
- Block quote
- Pre
-
+
+
+ Heading 1
+ Heading 2
+ Heading 3
+ Heading 4
+ Body
+ Small
+ Cite
+ Figure caption
+ Code
+ Block quote
+ Pre
+
-
-
-
-
-
+
+
+
+
+
+
From 57b302baf075e9209f69177ac7a7361537d601b4 Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Mon, 22 Jun 2026 14:35:15 +0100
Subject: [PATCH 07/10] go
---
.../example-codex-kitchen-sink/index.vue | 38 +++-----
.../lib/parse-tokens.ts | 92 ++++++++++++++++++-
.../lib/use-url-query-param.ts | 67 --------------
.../playground/PlaygroundSubTabs.vue | 39 ++++++++
.../playground/TokenSwatch.vue | 25 +++++
.../sections/ColorSection.vue | 32 +++++++
.../sections/TokensSection.vue | 85 ++++++++---------
.../sections/TypographySection.vue | 66 +++++++------
8 files changed, 270 insertions(+), 174 deletions(-)
delete mode 100644 src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue
diff --git a/src/prototypes/example-codex-kitchen-sink/index.vue b/src/prototypes/example-codex-kitchen-sink/index.vue
index 4beea41..558d1fa 100644
--- a/src/prototypes/example-codex-kitchen-sink/index.vue
+++ b/src/prototypes/example-codex-kitchen-sink/index.vue
@@ -1,6 +1,5 @@
@@ -95,12 +74,19 @@ main {
/* padding: 0px var(--spacing-100); */
/* margin: 0px 0px; */
/* padding: 0px 4px; */
+ /* padding-top: 4px; */
+ /* background-color: var(--background-color-base); */
}
.playground-tabs :deep(> .cdx-tabs__header) {
position: sticky;
top: 0px;
z-index: 2;
+ background-color: var(--background-color-base);
+ padding-top: 4px;
+ margin: 0px 0px;
+ padding-left: 4px;
+ padding-right: 4px;
}
article {
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
index 61fea8a..650f946 100644
--- a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
+++ b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
@@ -2,12 +2,15 @@ export type TokenKind =
| 'color-text'
| 'color-bg'
| 'color-border'
+ | 'color-accent'
| 'spacing'
| 'radius'
| 'font-size'
| 'font-weight'
| 'font-family'
| 'line-height'
+ | 'text-decoration'
+ | 'text-overflow'
| 'shadow'
| 'opacity'
| 'size'
@@ -41,6 +44,7 @@ export interface TokenFamilyGroup {
export function inferTokenKind(name: string): TokenKind {
if (name.startsWith('--background-color-')) return 'color-bg'
if (name.startsWith('--border-color-')) return 'color-border'
+ if (name.startsWith('--accent-color-')) return 'color-accent'
if (name.startsWith('--color-')) return 'color-text'
if (name.startsWith('--spacing-')) return 'spacing'
if (name.startsWith('--border-radius-')) return 'radius'
@@ -48,6 +52,8 @@ export function inferTokenKind(name: string): TokenKind {
if (name.startsWith('--font-weight-')) return 'font-weight'
if (name.startsWith('--font-family-')) return 'font-family'
if (name.startsWith('--line-height-')) return 'line-height'
+ if (name.startsWith('--text-decoration-')) return 'text-decoration'
+ if (name.startsWith('--text-overflow-')) return 'text-overflow'
if (name.startsWith('--box-shadow-')) return 'shadow'
if (name.startsWith('--opacity-')) return 'opacity'
if (
@@ -74,7 +80,13 @@ export function inferTokenFamily(name: string): TokenFamily {
return 'Color'
}
if (name.startsWith('--spacing-')) return 'Spacing'
- if (name.startsWith('--font-') || name.startsWith('--line-height-') || name.startsWith('--letter-spacing-')) {
+ if (
+ name.startsWith('--font-') ||
+ name.startsWith('--line-height-') ||
+ name.startsWith('--letter-spacing-') ||
+ name.startsWith('--text-decoration-') ||
+ name.startsWith('--text-overflow-')
+ ) {
return 'Typography'
}
if (name.startsWith('--border-radius-') || name.startsWith('--border-width-') || name.startsWith('--border-style-')) {
@@ -120,6 +132,7 @@ export function inferTokenCategory(name: string): string {
if (name.startsWith('--outline-')) return 'Outline'
if (name.startsWith('--mix-blend-mode-')) return 'Blend mode'
if (name.startsWith('--text-decoration-')) return 'Text decoration'
+ if (name.startsWith('--text-overflow-')) return 'Text overflow'
if (name.startsWith('--transform-')) return 'Transform'
if (name.startsWith('--accent-color-')) return 'Accent'
if (name.startsWith('--position-')) return 'Position'
@@ -233,6 +246,79 @@ export function getTokensForFamily(tokens: TokenEntry[], family: TokenFamily): T
return tokens.filter((token) => token.family === family)
}
-export function isTokenFamily(value: string): value is TokenFamily {
- return familyOrder.includes(value as TokenFamily)
+export type TypographySubTab =
+ | 'style'
+ | 'font'
+ | 'size'
+ | 'weight'
+ | 'line-height'
+ | 'decoration'
+ | 'overflow'
+
+const typographySubTabOrder: TypographySubTab[] = [
+ 'style',
+ 'font',
+ 'size',
+ 'weight',
+ 'line-height',
+ 'decoration',
+ 'overflow',
+]
+
+const typographySubTabLabels: Record = {
+ style: 'Style',
+ font: 'Font',
+ size: 'Size',
+ weight: 'Weight',
+ 'line-height': 'Line height',
+ decoration: 'Decoration',
+ overflow: 'Overflow',
+}
+
+export const typographySubTabs = typographySubTabOrder.map((id) => ({
+ id,
+ label: typographySubTabLabels[id],
+}))
+
+export function getTypographyTokensForSubTab(
+ tokens: TokenEntry[],
+ subTab: Exclude,
+): TokenEntry[] {
+ const kindBySubTab: Record, TokenKind> = {
+ font: 'font-family',
+ size: 'font-size',
+ weight: 'font-weight',
+ 'line-height': 'line-height',
+ decoration: 'text-decoration',
+ overflow: 'text-overflow',
+ }
+
+ return getTokensForFamily(tokens, 'Typography').filter((token) => token.kind === kindBySubTab[subTab])
+}
+
+export type ColorSubTab = 'text' | 'background' | 'border' | 'accent'
+
+const colorSubTabOrder: ColorSubTab[] = ['text', 'background', 'border', 'accent']
+
+const colorSubTabLabels: Record = {
+ text: 'Text',
+ background: 'Background',
+ border: 'Border',
+ accent: 'Accent',
+}
+
+export const colorSubTabs = colorSubTabOrder.map((id) => ({
+ id,
+ label: colorSubTabLabels[id],
+}))
+
+export function getColorTokensForSubTab(tokens: TokenEntry[], subTab: ColorSubTab): TokenEntry[] {
+ const kindBySubTab: Record = {
+ text: 'color-text',
+ background: 'color-bg',
+ border: 'color-border',
+ accent: 'color-accent',
+ }
+
+ return getTokensForFamily(tokens, 'Color').filter((token) => token.kind === kindBySubTab[subTab])
}
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts b/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
deleted file mode 100644
index bd9ac78..0000000
--- a/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
+++ /dev/null
@@ -1,67 +0,0 @@
-import { ref, watch, type Ref } from 'vue'
-import { useRoute, useRouter } from 'vue-router'
-
-function readQueryValue(raw: unknown): string | undefined {
- if (typeof raw === 'string') return raw
- if (Array.isArray(raw) && typeof raw[0] === 'string') return raw[0]
- return undefined
-}
-
-export { readQueryValue }
-
-export function useUrlQueryParam(
- key: string,
- defaultValue: T,
- isValid: (value: string) => value is T,
-): Ref {
- const route = useRoute()
- const router = useRouter()
-
- function parse(): T {
- const value = readQueryValue(route.query[key])
- if (value && isValid(value)) return value
- return defaultValue
- }
-
- const param = ref(parse()) as Ref
- let syncingFromRoute = false
-
- watch(
- () => route.query[key],
- () => {
- const next = parse()
- if (next === param.value) return
- syncingFromRoute = true
- param.value = next
- syncingFromRoute = false
- },
- )
-
- watch(param, (value) => {
- if (syncingFromRoute) return
-
- const normalized = value === defaultValue ? undefined : value
- const current = readQueryValue(route.query[key])
- if (current === normalized) return
-
- const query = { ...route.query }
- if (normalized === undefined) {
- delete query[key]
- } else {
- query[key] = normalized
- }
- void router.replace({ query })
- })
-
- return param
-}
-
-export function removeUrlQueryParam(key: string): void {
- const route = useRoute()
- const router = useRouter()
- if (!(key in route.query)) return
-
- const query = { ...route.query }
- delete query[key]
- void router.replace({ query })
-}
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue
new file mode 100644
index 0000000..7c53e43
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue b/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
index 3a86787..6a8599c 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
@@ -27,6 +27,11 @@ const cssVar = computed(() => `var(${props.token.name})`)
class="token-swatch__border-box"
:style="{ borderColor: cssVar }"
/>
+
`var(${props.token.name})`)
>
Line one
Line two
+
+ {{ token.value }}
+
+
+ Long text that overflows
+
`var(${props.token.name})`)
max-width: 100%;
}
+.token-swatch__text-overflow {
+ overflow: hidden;
+ white-space: nowrap;
+ max-width: 100%;
+}
+
.token-swatch__line-height {
width: 100%;
font-size: var(--font-size-small);
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue
new file mode 100644
index 0000000..618b136
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
index b09596d..16fd818 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
@@ -1,40 +1,51 @@
-
-
+
+
-
-
-
-
+
+
+
+
+
+
@@ -44,30 +55,6 @@ const activeFamily = useUrlQueryParam('sub', defaultFamily, isToken
-
-
+
+
-
-
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
index 6027625..bb1f1c8 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
@@ -1,40 +1,48 @@
-
-
- Heading 1
- Heading 2
- Heading 3
- Heading 4
- Body
- Small
- Cite
- Figure caption
- Code
- Block quote
- Pre
-
+
+
+
+ Heading 1
+ Heading 2
+ Heading 3
+ Heading 4
+ Body
+ Small
+ Cite
+ Figure caption
+ Code
+ Block quote
+ Pre
+
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
From f6427d65e921c6a5a7e4cbd0c53346b090be3e0d Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Tue, 23 Jun 2026 11:07:34 +0100
Subject: [PATCH 08/10] further work
---
.../references/buttons-and-actions.md | 2 +-
.../references/feedback-and-status.md | 4 +-
.../example-codex-kitchen-sink/index.vue | 61 +-
.../lib/color-contrast.ts | 99 +++
.../lib/component-tabs.ts | 70 ++
.../lib/fixtures.ts | 56 +-
.../lib/palette-colors.ts | 183 +++++
.../lib/parse-tokens.ts | 707 +++++++++++++++++-
.../lib/playground-tabs.ts | 132 ++++
.../lib/use-playground-leaf-tab.ts | 112 +++
.../lib/use-url-query-param.ts | 57 ++
.../playground/AnimationTokenList.vue | 368 +++++++++
.../playground/BorderTokenList.vue | 173 +++++
.../playground/BoxShadowTokenList.vue | 243 ++++++
.../playground/ColorPaletteList.vue | 184 +++++
.../playground/ColorTokenList.vue | 220 ++++++
.../playground/CursorTokenList.vue | 94 +++
.../playground/DimensionTokenList.vue | 382 ++++++++++
.../playground/FontFamilyList.vue | 48 ++
.../playground/FontSizeList.vue | 48 ++
.../playground/FontWeightList.vue | 49 ++
.../playground/LineHeightList.vue | 68 ++
.../playground/OpacityTokenList.vue | 175 +++++
.../playground/OutlineTokenList.vue | 109 +++
.../playground/PlaygroundCell.vue | 26 +-
.../playground/PlaygroundList.vue | 13 +
.../playground/PlaygroundSection.vue | 2 +-
.../playground/PlaygroundSubTabs.vue | 15 +-
.../playground/PositionTokenList.vue | 122 +++
.../playground/TextDecorationList.vue | 49 ++
.../playground/TextOverflowList.vue | 77 ++
.../playground/TokenDeprecatedLabel.vue | 14 +
.../playground/TokenSwatch.vue | 14 +-
.../playground/ZIndexTokenList.vue | 179 +++++
.../sections/ButtonsSection.vue | 405 +++++-----
.../sections/ColorSection.vue | 25 +-
.../sections/ContentDataSection.vue | 261 +++++++
.../sections/FeedbackSection.vue | 142 ++--
.../sections/FormElementsSection.vue | 268 +++++++
.../sections/IconsSection.vue | 104 ++-
.../sections/InputsSection.vue | 283 -------
.../sections/LayoutSection.vue | 136 ----
.../sections/MediaSection.vue | 51 ++
.../sections/NavigationSection.vue | 136 ++++
.../sections/OverlaysSection.vue | 184 -----
.../sections/SearchSection.vue | 117 +++
.../sections/TokenSection.vue | 96 +++
.../sections/TokensSection.vue | 60 --
.../sections/TypographySection.vue | 51 +-
49 files changed, 5488 insertions(+), 986 deletions(-)
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/color-contrast.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/component-tabs.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/palette-colors.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/playground-tabs.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/use-playground-leaf-tab.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/BoxShadowTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/ColorPaletteList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/ColorTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/CursorTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/FontFamilyList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/FontSizeList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/FontWeightList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/LineHeightList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/OpacityTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/OutlineTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PlaygroundList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PositionTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/TextDecorationList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/TextOverflowList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/TokenDeprecatedLabel.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/ZIndexTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/ContentDataSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/FormElementsSection.vue
delete mode 100644 src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue
delete mode 100644 src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/MediaSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/NavigationSection.vue
delete mode 100644 src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/SearchSection.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue
delete mode 100644 src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
diff --git a/.agents/skills/codex-components/references/buttons-and-actions.md b/.agents/skills/codex-components/references/buttons-and-actions.md
index b820633..e693500 100644
--- a/.agents/skills/codex-components/references/buttons-and-actions.md
+++ b/.agents/skills/codex-components/references/buttons-and-actions.md
@@ -94,7 +94,7 @@ descriptor).
| Prop | Values |
| --- | --- |
| `icon` | required: an icon import or icon descriptor |
-| `size` | `xx-small` / `x-small` / `small` / `medium` (default) |
+| `size` | `x-small` / `small` / `medium` (default) |
| `iconLabel` | accessible label when used standalone |
| `dir` | `ltr` / `rtl` (auto-flips bidi-aware icons) |
| `langCode` | for langCodeMap-aware icons (e.g., bold-x icons) |
diff --git a/.agents/skills/codex-components/references/feedback-and-status.md b/.agents/skills/codex-components/references/feedback-and-status.md
index 22c2796..8faedc4 100644
--- a/.agents/skills/codex-components/references/feedback-and-status.md
+++ b/.agents/skills/codex-components/references/feedback-and-status.md
@@ -33,13 +33,13 @@ A progress bar.
```vue
-
+
```
| Prop | Values | Default |
| --- | --- | --- |
| `inline` | true for thin inline bar | `false` |
-| `progress` | `0`–`100` for determinate, omit for indeterminate | indeterminate |
+| `value` | `0`–`100` for determinate, omit for indeterminate | indeterminate |
| `disabled` | boolean | `false` |
## CdxProgressIndicator
diff --git a/src/prototypes/example-codex-kitchen-sink/index.vue b/src/prototypes/example-codex-kitchen-sink/index.vue
index 558d1fa..0cd97a9 100644
--- a/src/prototypes/example-codex-kitchen-sink/index.vue
+++ b/src/prototypes/example-codex-kitchen-sink/index.vue
@@ -2,14 +2,23 @@
import { ref, type Component } from 'vue'
import { CdxTab, CdxTabs, CdxToastContainer } from '@wikimedia/codex'
+import { parseLeafTab, type MainTabId } from './lib/playground-tabs'
+import {
+ providePlaygroundLeafTab,
+ syncMainTabWithLeafTab,
+ usePlaygroundLeafTab,
+} from './lib/use-playground-leaf-tab'
+
import ButtonsSection from './sections/ButtonsSection.vue'
import FeedbackSection from './sections/FeedbackSection.vue'
import IconsSection from './sections/IconsSection.vue'
-import InputsSection from './sections/InputsSection.vue'
-import LayoutSection from './sections/LayoutSection.vue'
-import OverlaysSection from './sections/OverlaysSection.vue'
+import FormElementsSection from './sections/FormElementsSection.vue'
+import MediaSection from './sections/MediaSection.vue'
+import NavigationSection from './sections/NavigationSection.vue'
+import SearchSection from './sections/SearchSection.vue'
+import ContentDataSection from './sections/ContentDataSection.vue'
import ColorSection from './sections/ColorSection.vue'
-import TokensSection from './sections/TokensSection.vue'
+import TokenSection from './sections/TokenSection.vue'
import TypographySection from './sections/TypographySection.vue'
definePage({
@@ -22,30 +31,48 @@ definePage({
const navItems = [
{ id: 'typography', label: 'Typography' },
{ id: 'color', label: 'Color' },
- { id: 'tokens', label: 'Tokens' },
+ { id: 'tokens-layout', label: 'Layout' },
+ { id: 'tokens-appearance', label: 'Appearance' },
+ { id: 'tokens-animation', label: 'Animation' },
{ id: 'icons', label: 'Icons' },
{ id: 'components-buttons', label: 'Buttons' },
- { id: 'components-inputs', label: 'Inputs' },
+ { id: 'components-form-elements', label: 'Form elements' },
{ id: 'components-feedback', label: 'Feedback' },
- { id: 'components-overlays', label: 'Overlays' },
- { id: 'components-layout', label: 'Layout' },
+ { id: 'components-content-data', label: 'Content & data' },
+ { id: 'components-media', label: 'Media' },
+ { id: 'components-navigation', label: 'Navigation' },
+ { id: 'components-search', label: 'Search' },
] as const
const sectionByTab: Record<(typeof navItems)[number]['id'], Component> = {
typography: TypographySection,
color: ColorSection,
- tokens: TokensSection,
+ 'tokens-layout': TokenSection,
+ 'tokens-appearance': TokenSection,
+ 'tokens-animation': TokenSection,
icons: IconsSection,
'components-buttons': ButtonsSection,
- 'components-inputs': InputsSection,
+ 'components-form-elements': FormElementsSection,
'components-feedback': FeedbackSection,
- 'components-overlays': OverlaysSection,
- 'components-layout': LayoutSection,
+ 'components-content-data': ContentDataSection,
+ 'components-media': MediaSection,
+ 'components-navigation': NavigationSection,
+ 'components-search': SearchSection,
+}
+
+const tokenSectionByTab: Partial> = {
+ 'tokens-layout': 'layout',
+ 'tokens-appearance': 'appearance',
+ 'tokens-animation': 'animation',
}
-type TabId = (typeof navItems)[number]['id']
+type TabId = MainTabId
+
+const leafTab = usePlaygroundLeafTab()
+const { subTabMemory } = providePlaygroundLeafTab(leafTab)
-const activeTab = ref('typography')
+const activeTab = ref(parseLeafTab(leafTab.value)?.main ?? 'typography')
+syncMainTabWithLeafTab(activeTab, leafTab, subTabMemory)
@@ -55,7 +82,11 @@ const activeTab = ref('typography')
-
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/color-contrast.ts b/src/prototypes/example-codex-kitchen-sink/lib/color-contrast.ts
new file mode 100644
index 0000000..46127f3
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/color-contrast.ts
@@ -0,0 +1,99 @@
+interface Rgba {
+ r: number
+ g: number
+ b: number
+ a: number
+}
+
+function parseColor(color: string): Rgba | null {
+ if (!color || color === 'transparent') {
+ return { r: 0, g: 0, b: 0, a: 0 }
+ }
+
+ const rgbaMatch = color.match(
+ /rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)(?:[,\s/]+([\d.]+))?\s*\)/,
+ )
+ if (rgbaMatch) {
+ return {
+ r: Number(rgbaMatch[1]),
+ g: Number(rgbaMatch[2]),
+ b: Number(rgbaMatch[3]),
+ a: rgbaMatch[4] !== undefined ? Number(rgbaMatch[4]) : 1,
+ }
+ }
+
+ const hexMatch = color.match(/^#([0-9a-f]{3,8})$/i)
+ if (!hexMatch) return null
+
+ let hex = hexMatch[1]
+ if (hex.length === 3) {
+ hex = hex
+ .split('')
+ .map((char) => char + char)
+ .join('')
+ }
+
+ if (hex.length < 6) return null
+
+ return {
+ r: parseInt(hex.slice(0, 2), 16),
+ g: parseInt(hex.slice(2, 4), 16),
+ b: parseInt(hex.slice(4, 6), 16),
+ a: 1,
+ }
+}
+
+function relativeLuminance(r: number, g: number, b: number): number {
+ const channel = (value: number) => {
+ const normalized = value / 255
+ return normalized <= 0.03928
+ ? normalized / 12.92
+ : ((normalized + 0.055) / 1.055) ** 2.4
+ }
+
+ return 0.2126 * channel(r) + 0.7152 * channel(g) + 0.0722 * channel(b)
+}
+
+function compositeOver(fg: Rgba, bg: Rgba): [number, number, number] {
+ if (fg.a <= 0) return [bg.r, bg.g, bg.b]
+ if (fg.a >= 1) return [fg.r, fg.g, fg.b]
+
+ const backdropAlpha = bg.a * (1 - fg.a)
+ const outAlpha = fg.a + backdropAlpha
+ if (outAlpha <= 0) return [bg.r, bg.g, bg.b]
+
+ return [
+ Math.round((fg.r * fg.a + bg.r * backdropAlpha) / outAlpha),
+ Math.round((fg.g * fg.a + bg.g * backdropAlpha) / outAlpha),
+ Math.round((fg.b * fg.a + bg.b * backdropAlpha) / outAlpha),
+ ]
+}
+
+function getCanvasColor(): Rgba | null {
+ return parseColor(getComputedStyle(document.documentElement).backgroundColor)
+}
+
+function resolvedBackgroundLuminance(element: HTMLElement): number | null {
+ const canvasColor = getCanvasColor()
+ const backgroundColor = parseColor(getComputedStyle(element).backgroundColor)
+ if (!backgroundColor || !canvasColor) return null
+
+ const [r, g, b] = compositeOver(backgroundColor, canvasColor)
+ return relativeLuminance(r, g, b)
+}
+
+export type SwatchTextTone = 'light' | 'dark'
+
+export function getSwatchTextToneForBackground(element: HTMLElement): SwatchTextTone {
+ const luminance = resolvedBackgroundLuminance(element)
+ if (luminance === null) return 'dark'
+
+ return luminance > 0.5 ? 'dark' : 'light'
+}
+
+export function needsInvertedTextBackground(element: HTMLElement): boolean {
+ const color = parseColor(getComputedStyle(element).color)
+ if (!color) return false
+
+ return relativeLuminance(color.r, color.g, color.b) > 0.65
+}
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/component-tabs.ts b/src/prototypes/example-codex-kitchen-sink/lib/component-tabs.ts
new file mode 100644
index 0000000..62a5897
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/component-tabs.ts
@@ -0,0 +1,70 @@
+export const buttonsSubTabs = [
+ { id: 'button', label: 'Button' },
+ { id: 'button-group', label: 'ButtonGroup' },
+ { id: 'menu-button', label: 'MenuButton' },
+ { id: 'toggle-button', label: 'ToggleButton' },
+ { id: 'toggle-button-group', label: 'ToggleButtonGroup' },
+] as const
+
+export const formElementsSubTabs = [
+ { id: 'checkbox', label: 'Checkbox' },
+ { id: 'chip-input', label: 'ChipInput' },
+ { id: 'combobox', label: 'Combobox' },
+ { id: 'field', label: 'Field' },
+ { id: 'label', label: 'Label' },
+ { id: 'lookup', label: 'Lookup' },
+ { id: 'multiselect-lookup', label: 'MultiselectLookup' },
+ { id: 'radio', label: 'Radio' },
+ { id: 'select', label: 'Select' },
+ { id: 'text-area', label: 'TextArea' },
+ { id: 'text-input', label: 'TextInput' },
+ { id: 'toggle-switch', label: 'ToggleSwitch' },
+] as const
+
+export const contentDataSubTabs = [
+ { id: 'accordion', label: 'Accordion' },
+ { id: 'card', label: 'Card' },
+ { id: 'dialog', label: 'Dialog' },
+ { id: 'menu', label: 'Menu' },
+ { id: 'menu-item', label: 'MenuItem' },
+ { id: 'popover', label: 'Popover' },
+ { id: 'table', label: 'Table' },
+ { id: 'tooltip', label: 'Tooltip' },
+] as const
+
+export const feedbackSubTabs = [
+ { id: 'info-chip', label: 'InfoChip' },
+ { id: 'message', label: 'Message' },
+ { id: 'progress-bar', label: 'ProgressBar' },
+ { id: 'progress-indicator', label: 'ProgressIndicator' },
+ { id: 'toast', label: 'Toast' },
+] as const
+
+export const mediaSubTabs = [
+ { id: 'image', label: 'Image' },
+ { id: 'thumbnail', label: 'Thumbnail' },
+] as const
+
+export const navigationSubTabs = [
+ { id: 'link', label: 'Link' },
+ { id: 'tabs', label: 'Tabs' },
+] as const
+
+export const searchSubTabs = [
+ { id: 'search-input', label: 'SearchInput' },
+ { id: 'typeahead-search', label: 'TypeaheadSearch' },
+ { id: 'search-result-title', label: 'SearchResultTitle' },
+] as const
+
+export const iconsSubTabs = [
+ { id: 'size', label: 'Size' },
+ { id: 'type', label: 'Type' },
+] as const
+
+/** Codex 2.x IconSizes — xx-small was removed and no longer applies sizing. */
+export const iconSizeEntries = [
+ { id: 'x-small', deprecated: false },
+ { id: 'small', deprecated: false },
+ { id: 'medium', deprecated: false },
+ { id: 'xx-small', deprecated: true },
+] as const
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts b/src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts
index e26ecca..debc51e 100644
--- a/src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts
+++ b/src/prototypes/example-codex-kitchen-sink/lib/fixtures.ts
@@ -36,17 +36,63 @@ export const tableRows: TableRow[] = [
{ id: '3', title: 'Rhine', status: 'Published', views: 890 },
]
-export const searchResults: SearchResult[] = [
- { label: 'Albert Einstein', title: 'Albert Einstein', description: 'German-born theoretical physicist' },
- { label: 'Albert Camus', title: 'Albert Camus', description: 'French philosopher and author' },
-]
-
const wetLegImageUrl = `${import.meta.env.BASE_URL}images/wet-leg-o2-infobox.jpg`
export const thumbnailUrl = wetLegImageUrl
export const imageUrl = wetLegImageUrl
+export const typeaheadSearchCatalog: SearchResult[] = [
+ {
+ value: 'Albert Einstein',
+ label: 'Albert Einstein',
+ description: 'German-born theoretical physicist',
+ url: 'https://en.wikipedia.org/wiki/Albert_Einstein',
+ thumbnail: { url: thumbnailUrl, width: 40, height: 40 },
+ },
+ {
+ value: 'Albert Camus',
+ label: 'Albert Camus',
+ description: 'French philosopher and author',
+ url: 'https://en.wikipedia.org/wiki/Albert_Camus',
+ },
+ {
+ value: 'Alberta',
+ label: 'Alberta',
+ description: 'Province of Canada',
+ url: 'https://en.wikipedia.org/wiki/Alberta',
+ },
+ {
+ value: 'Albert, Prince Consort',
+ label: 'Albert, Prince Consort',
+ description: 'Consort of Queen Victoria',
+ url: 'https://en.wikipedia.org/wiki/Albert,_Prince_Consort',
+ },
+ {
+ value: 'Albrecht Dürer',
+ label: 'Albrecht Dürer',
+ description: 'German painter and printmaker',
+ url: 'https://en.wikipedia.org/wiki/Albrecht_D%C3%BCrer',
+ },
+ {
+ value: 'Alberta (disambiguation)',
+ label: 'Alberta (disambiguation)',
+ description: 'Topics referred to by the same term',
+ url: 'https://en.wikipedia.org/wiki/Alberta_(disambiguation)',
+ },
+]
+
+export function filterTypeaheadSearchResults(query: string): SearchResult[] {
+ const trimmed = query.trim().toLowerCase()
+ if (!trimmed.length) return []
+
+ return typeaheadSearchCatalog.filter((result) => {
+ const label = String(result.label ?? result.value).toLowerCase()
+ const description = result.description?.toLowerCase() ?? ''
+ return label.includes(trimmed) || description.includes(trimmed)
+ })
+}
+
export const buttonGroupItems = [
{ value: 'edit', label: 'Edit' },
{ value: 'history', label: 'History' },
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/palette-colors.ts b/src/prototypes/example-codex-kitchen-sink/lib/palette-colors.ts
new file mode 100644
index 0000000..a5a22e0
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/palette-colors.ts
@@ -0,0 +1,183 @@
+export interface PaletteColor {
+ name: string
+ value: string
+}
+
+export interface PaletteGroup {
+ family: string
+ colors: PaletteColor[]
+}
+
+/** Raw Codex palette from https://doc.wikimedia.org/codex/latest/style-guide/colors.html */
+export const codexPaletteGroups: PaletteGroup[] = [
+ {
+ family: 'White',
+ colors: [{ name: 'white', value: '#fff' }],
+ },
+ {
+ family: 'Black',
+ colors: [{ name: 'black', value: '#000' }],
+ },
+ {
+ family: 'Gray',
+ colors: [
+ { name: 'gray50', value: '#f8f9fa' },
+ { name: 'gray100', value: '#eaecf0' },
+ { name: 'gray200', value: '#dadde3' },
+ { name: 'gray300', value: '#c8ccd1' },
+ { name: 'gray400', value: '#a2a9b1' },
+ { name: 'gray500', value: '#72777d' },
+ { name: 'gray600', value: '#54595d' },
+ { name: 'gray700', value: '#404244' },
+ { name: 'gray800', value: '#27292d' },
+ { name: 'gray900', value: '#202122' },
+ { name: 'gray1000', value: '#101418' },
+ ],
+ },
+ {
+ family: 'Red',
+ colors: [
+ { name: 'red50', value: '#ffe9e5' },
+ { name: 'red100', value: '#ffdad3' },
+ { name: 'red200', value: '#ffc8bd' },
+ { name: 'red300', value: '#fea898' },
+ { name: 'red400', value: '#fd7865' },
+ { name: 'red500', value: '#f54739' },
+ { name: 'red600', value: '#d74032' },
+ { name: 'red700', value: '#bf3c2c' },
+ { name: 'red800', value: '#9f3526' },
+ { name: 'red900', value: '#612419' },
+ { name: 'red1000', value: '#3c1a13' },
+ ],
+ },
+ {
+ family: 'Orange',
+ colors: [
+ { name: 'orange50', value: '#ffead4' },
+ { name: 'orange100', value: '#ffdcb8' },
+ { name: 'orange200', value: '#ffc894' },
+ { name: 'orange300', value: '#ffa758' },
+ { name: 'orange400', value: '#f97f26' },
+ { name: 'orange500', value: '#d46926' },
+ { name: 'orange600', value: '#bb5c26' },
+ { name: 'orange700', value: '#a95226' },
+ { name: 'orange800', value: '#8e4424' },
+ { name: 'orange900', value: '#572c19' },
+ { name: 'orange1000', value: '#361d13' },
+ ],
+ },
+ {
+ family: 'Yellow',
+ colors: [
+ { name: 'yellow50', value: '#fdf2d5' },
+ { name: 'yellow100', value: '#ffe49c' },
+ { name: 'yellow200', value: '#ffcf4f' },
+ { name: 'yellow300', value: '#edb537' },
+ { name: 'yellow400', value: '#ca982e' },
+ { name: 'yellow500', value: '#ab7f2a' },
+ { name: 'yellow600', value: '#987027' },
+ { name: 'yellow700', value: '#886425' },
+ { name: 'yellow800', value: '#735421' },
+ { name: 'yellow900', value: '#453217' },
+ { name: 'yellow1000', value: '#2d2212' },
+ ],
+ },
+ {
+ family: 'Lime',
+ colors: [
+ { name: 'lime50', value: '#e3f2e4' },
+ { name: 'lime100', value: '#d1e9d2' },
+ { name: 'lime200', value: '#b9debc' },
+ { name: 'lime300', value: '#94cb9a' },
+ { name: 'lime400', value: '#5db26c' },
+ { name: 'lime500', value: '#259948' },
+ { name: 'lime600', value: '#1f893f' },
+ { name: 'lime700', value: '#1f7a39' },
+ { name: 'lime800', value: '#1f6631' },
+ { name: 'lime900', value: '#183f20' },
+ { name: 'lime1000', value: '#142817' },
+ ],
+ },
+ {
+ family: 'Green',
+ colors: [
+ { name: 'green50', value: '#dff2eb' },
+ { name: 'green100', value: '#c9eadd' },
+ { name: 'green200', value: '#aedfcd' },
+ { name: 'green300', value: '#80cdb3' },
+ { name: 'green400', value: '#2cb491' },
+ { name: 'green500', value: '#099979' },
+ { name: 'green600', value: '#14876b' },
+ { name: 'green700', value: '#177860' },
+ { name: 'green800', value: '#196551' },
+ { name: 'green900', value: '#153d31' },
+ { name: 'green1000', value: '#132821' },
+ ],
+ },
+ {
+ family: 'Blue',
+ colors: [
+ { name: 'blue50', value: '#e8eeff' },
+ { name: 'blue100', value: '#d9e2ff' },
+ { name: 'blue200', value: '#b6d4fb' },
+ { name: 'blue300', value: '#a6bbf5' },
+ { name: 'blue400', value: '#88a3e8' },
+ { name: 'blue500', value: '#6485d1' },
+ { name: 'blue600', value: '#4b77d6' },
+ { name: 'blue700', value: '#36c' },
+ { name: 'blue800', value: '#3056a9' },
+ { name: 'blue900', value: '#233566' },
+ { name: 'blue1000', value: '#1b223d' },
+ ],
+ },
+ {
+ family: 'Purple',
+ colors: [
+ { name: 'purple50', value: '#f0ecf6' },
+ { name: 'purple100', value: '#e6e0f0' },
+ { name: 'purple200', value: '#d9d0e9' },
+ { name: 'purple300', value: '#c5b9dd' },
+ { name: 'purple400', value: '#a799cd' },
+ { name: 'purple500', value: '#8d7ebd' },
+ { name: 'purple600', value: '#7a6db7' },
+ { name: 'purple700', value: '#6a60b0' },
+ { name: 'purple800', value: '#534fa3' },
+ { name: 'purple900', value: '#353262' },
+ { name: 'purple1000', value: '#23203b' },
+ ],
+ },
+ {
+ family: 'Pink',
+ colors: [
+ { name: 'pink50', value: '#f5ebf2' },
+ { name: 'pink100', value: '#eedeea' },
+ { name: 'pink200', value: '#e6cede' },
+ { name: 'pink300', value: '#d9b4cd' },
+ { name: 'pink400', value: '#c690b4' },
+ { name: 'pink500', value: '#b5739e' },
+ { name: 'pink600', value: '#ac5c90' },
+ { name: 'pink700', value: '#9b527f' },
+ { name: 'pink800', value: '#82456a' },
+ { name: 'pink900', value: '#4e2c40' },
+ { name: 'pink1000', value: '#311e28' },
+ ],
+ },
+ {
+ family: 'Maroon',
+ colors: [
+ { name: 'maroon50', value: '#f6ebeb' },
+ { name: 'maroon100', value: '#f0dedd' },
+ { name: 'maroon200', value: '#e8cecd' },
+ { name: 'maroon300', value: '#dcb5b3' },
+ { name: 'maroon400', value: '#c99391' },
+ { name: 'maroon500', value: '#b57775' },
+ { name: 'maroon600', value: '#ac6262' },
+ { name: 'maroon700', value: '#9f5555' },
+ { name: 'maroon800', value: '#854848' },
+ { name: 'maroon900', value: '#512e2e' },
+ { name: 'maroon1000', value: '#321f1e' },
+ ],
+ },
+]
+
+export const codexPaletteColors = codexPaletteGroups.flatMap((group) => group.colors)
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
index 650f946..6bf3fca 100644
--- a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
+++ b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
@@ -34,6 +34,7 @@ export interface TokenEntry {
category: string
kind: TokenKind
family: TokenFamily
+ deprecated: boolean
}
export interface TokenFamilyGroup {
@@ -140,16 +141,41 @@ export function inferTokenCategory(name: string): string {
return 'Other'
}
+export function sortTokensWithDeprecatedLast(
+ tokens: TokenEntry[],
+ compare: (a: TokenEntry, b: TokenEntry) => number = (a, b) => a.name.localeCompare(b.name),
+): TokenEntry[] {
+ return [...tokens].sort((a, b) => {
+ if (a.deprecated !== b.deprecated) return a.deprecated ? 1 : -1
+ return compare(a, b)
+ })
+}
+
+function parseDeprecatedTokenNames(cssBlock: string): Set {
+ const deprecated = new Set()
+ const re =
+ /\/\*\s*Warning:\s*the following token name is deprecated[^*]*\*\/\s*\n\s*(--[a-z0-9-]+):/g
+ let match: RegExpExecArray | null
+
+ while ((match = re.exec(cssBlock)) !== null) {
+ deprecated.add(match[1])
+ }
+
+ return deprecated
+}
+
export function parseTokensFromCss(css: string): TokenEntry[] {
const rootMatch = css.match(/:root\s*\{([\s\S]*?)\n\}/)
if (!rootMatch) return []
+ const rootBlock = rootMatch[1]
+ const deprecatedNames = parseDeprecatedTokenNames(rootBlock)
const seen = new Set()
const tokens: TokenEntry[] = []
const re = /^\s*(--[a-z0-9-]+):\s*([^;]+);/gm
let match: RegExpExecArray | null
- while ((match = re.exec(rootMatch[1])) !== null) {
+ while ((match = re.exec(rootBlock)) !== null) {
const name = match[1]
if (seen.has(name)) continue
seen.add(name)
@@ -159,10 +185,11 @@ export function parseTokensFromCss(css: string): TokenEntry[] {
category: inferTokenCategory(name),
kind: inferTokenKind(name),
family: inferTokenFamily(name),
+ deprecated: deprecatedNames.has(name),
})
}
- return tokens.sort((a, b) => a.name.localeCompare(b.name))
+ return sortTokensWithDeprecatedLast(tokens)
}
const categoryOrder = [
@@ -213,7 +240,10 @@ function groupByCategory(tokens: TokenEntry[]): { category: string; tokens: Toke
return categoryOrder
.filter((category) => map.has(category))
- .map((category) => ({ category, tokens: map.get(category)! }))
+ .map((category) => ({
+ category,
+ tokens: sortTokensWithDeprecatedLast(map.get(category)!),
+ }))
}
export function groupTokensByCategory(tokens: TokenEntry[]): { category: string; tokens: TokenEntry[] }[] {
@@ -293,18 +323,85 @@ export function getTypographyTokensForSubTab(
overflow: 'text-overflow',
}
- return getTokensForFamily(tokens, 'Typography').filter((token) => token.kind === kindBySubTab[subTab])
+ const filtered = getTokensForFamily(tokens, 'Typography').filter(
+ (token) => token.kind === kindBySubTab[subTab],
+ )
+
+ if (subTab === 'size') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) => parseFontSizeValue(a.value) - parseFontSizeValue(b.value))
+ }
+
+ if (subTab === 'weight') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) => parseFontWeightValue(a.value) - parseFontWeightValue(b.value))
+ }
+
+ if (subTab === 'line-height') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) => lineHeightSortIndex(a.name) - lineHeightSortIndex(b.name))
+ }
+
+ if (subTab === 'decoration') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) => textDecorationSortIndex(a.name) - textDecorationSortIndex(b.name))
+ }
+
+ if (subTab === 'overflow') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) => textOverflowSortIndex(a.name) - textOverflowSortIndex(b.name))
+ }
+
+ return sortTokensWithDeprecatedLast(filtered)
}
-export type ColorSubTab = 'text' | 'background' | 'border' | 'accent'
+const lineHeightScaleOrder = [
+ 'x-small',
+ 'small',
+ 'medium',
+ 'content',
+ 'large',
+ 'x-large',
+ 'xx-large',
+ 'xxx-large',
+] as const
-const colorSubTabOrder: ColorSubTab[] = ['text', 'background', 'border', 'accent']
+function lineHeightSortIndex(name: string): number {
+ const suffix = name.slice('--line-height-'.length)
+ const index = lineHeightScaleOrder.indexOf(suffix as (typeof lineHeightScaleOrder)[number])
+ return index === -1 ? lineHeightScaleOrder.length : index
+}
+
+const textDecorationOrder = ['none', 'underline', 'line-through'] as const
+
+function textDecorationSortIndex(name: string): number {
+ const suffix = name.slice('--text-decoration-'.length)
+ const index = textDecorationOrder.indexOf(suffix as (typeof textDecorationOrder)[number])
+ return index === -1 ? textDecorationOrder.length : index
+}
+
+const textOverflowOrder = ['clip', 'ellipsis'] as const
+
+function textOverflowSortIndex(name: string): number {
+ const suffix = name.slice('--text-overflow-'.length)
+ const index = textOverflowOrder.indexOf(suffix as (typeof textOverflowOrder)[number])
+ return index === -1 ? textOverflowOrder.length : index
+}
+
+function parseFontSizeValue(value: string): number {
+ const match = value.match(/^([\d.]+)rem$/)
+ return match ? parseFloat(match[1]) : 0
+}
+
+function parseFontWeightValue(value: string): number {
+ return parseFloat(value) || 0
+}
+
+export type ColorSubTab = 'text' | 'background' | 'border' | 'accent' | 'palette'
+
+const colorSubTabOrder: ColorSubTab[] = ['text', 'background', 'border', 'accent', 'palette']
const colorSubTabLabels: Record = {
text: 'Text',
background: 'Background',
border: 'Border',
accent: 'Accent',
+ palette: 'Palette',
}
export const colorSubTabs = colorSubTabOrder.map((id) => ({
@@ -313,12 +410,606 @@ export const colorSubTabs = colorSubTabOrder.map((id) => ({
}))
export function getColorTokensForSubTab(tokens: TokenEntry[], subTab: ColorSubTab): TokenEntry[] {
- const kindBySubTab: Record = {
+ if (subTab === 'palette') return []
+
+ const kindBySubTab: Record, TokenKind> = {
text: 'color-text',
background: 'color-bg',
border: 'color-border',
accent: 'color-accent',
}
- return getTokensForFamily(tokens, 'Color').filter((token) => token.kind === kindBySubTab[subTab])
+ return sortTokensWithDeprecatedLast(
+ getTokensForFamily(tokens, 'Color').filter((token) => token.kind === kindBySubTab[subTab]),
+ )
+}
+
+export type TokenSection = 'layout' | 'appearance' | 'animation'
+
+export type LayoutSubTab =
+ | 'spacing'
+ | 'size'
+ | 'position'
+ | 'breakpoint'
+ | 'z-index'
+ | 'box-sizing'
+
+export type AppearanceSubTab = 'border' | 'box-shadow' | 'cursor' | 'opacity' | 'outline'
+
+export type AnimationSubTab = 'animation' | 'transition'
+
+export type TokenSubTab = LayoutSubTab | AppearanceSubTab | AnimationSubTab
+
+const layoutSubTabOrder: LayoutSubTab[] = [
+ 'spacing',
+ 'size',
+ 'position',
+ 'breakpoint',
+ 'z-index',
+ 'box-sizing',
+]
+
+const appearanceSubTabOrder: AppearanceSubTab[] = [
+ 'border',
+ 'box-shadow',
+ 'cursor',
+ 'opacity',
+ 'outline',
+]
+
+const animationSubTabOrder: AnimationSubTab[] = ['animation', 'transition']
+
+const layoutSubTabLabels: Record = {
+ spacing: 'Spacing',
+ size: 'Size',
+ position: 'Position',
+ breakpoint: 'Breakpoint',
+ 'z-index': 'Z-index',
+ 'box-sizing': 'Box-sizing',
+}
+
+const appearanceSubTabLabels: Record = {
+ border: 'Border',
+ 'box-shadow': 'Box-shadow',
+ cursor: 'Cursor',
+ opacity: 'Opacity',
+ outline: 'Outline',
+}
+
+const animationSubTabLabels: Record = {
+ animation: 'Animation',
+ transition: 'Transition',
+}
+
+export const tokenSectionTabs = [
+ {
+ id: 'layout' as const,
+ label: 'Layout',
+ subTabs: layoutSubTabOrder.map((id) => ({ id, label: layoutSubTabLabels[id] })),
+ },
+ {
+ id: 'appearance' as const,
+ label: 'Appearance',
+ subTabs: appearanceSubTabOrder.map((id) => ({ id, label: appearanceSubTabLabels[id] })),
+ },
+ {
+ id: 'animation' as const,
+ label: 'Animation',
+ subTabs: animationSubTabOrder.map((id) => ({ id, label: animationSubTabLabels[id] })),
+ },
+]
+
+const borderShorthandNames = new Set([
+ '--border-base',
+ '--border-subtle',
+ '--border-progressive',
+ '--border-destructive',
+ '--border-transparent',
+])
+
+export type BorderTokenGroup = 'Shorthand' | 'Radius' | 'Style' | 'Width'
+
+export type TransitionTokenGroup = 'Property' | 'Timing' | 'Duration'
+
+export type AnimationTokenGroup = 'Timing' | 'Iteration' | 'Duration' | 'Delay' | 'Transform'
+
+export function getBorderTokenGroup(name: string): BorderTokenGroup {
+ if (borderShorthandNames.has(name)) return 'Shorthand'
+ if (name.startsWith('--border-radius-')) return 'Radius'
+ if (name.startsWith('--border-style-')) return 'Style'
+ return 'Width'
+}
+
+export function getTransitionTokenGroup(name: string): TransitionTokenGroup {
+ if (name.startsWith('--transition-property-')) return 'Property'
+ if (name.startsWith('--transition-timing-function-')) return 'Timing'
+ return 'Duration'
+}
+
+export function getAnimationTokenGroup(name: string): AnimationTokenGroup {
+ if (name.startsWith('--animation-timing-function-')) return 'Timing'
+ if (name.startsWith('--animation-iteration-count-')) return 'Iteration'
+ if (name.startsWith('--animation-duration-')) return 'Duration'
+ if (name.startsWith('--animation-delay-')) return 'Delay'
+ return 'Transform'
+}
+
+const borderTokenOrder = [
+ '--border-base',
+ '--border-subtle',
+ '--border-progressive',
+ '--border-destructive',
+ '--border-transparent',
+ '--border-radius-sharp',
+ '--border-radius-base',
+ '--border-radius-pill',
+ '--border-radius-circle',
+ '--border-style-base',
+ '--border-style-dashed',
+ '--border-width-base',
+ '--border-width-thick',
+ '--border-width-input-radio--checked',
+] as const
+
+export type BoxShadowTokenGroup = 'Color' | 'Inset' | 'Outset' | 'Presets'
+
+export function getBoxShadowTokenGroup(name: string): BoxShadowTokenGroup {
+ if (name.startsWith('--box-shadow-color-')) return 'Color'
+ if (name.startsWith('--box-shadow-inset-')) return 'Inset'
+ if (name.startsWith('--box-shadow-outset-')) return 'Outset'
+ return 'Presets'
+}
+
+const boxShadowTokenOrder = [
+ '--box-shadow-color-alpha-base',
+ '--box-shadow-color-base',
+ '--box-shadow-color-destructive--focus',
+ '--box-shadow-color-inverted',
+ '--box-shadow-color-progressive--active',
+ '--box-shadow-color-progressive--focus',
+ '--box-shadow-color-progressive-selected',
+ '--box-shadow-color-progressive-selected--active',
+ '--box-shadow-color-progressive-selected--hover',
+ '--box-shadow-color-transparent',
+ '--box-shadow-inset-small',
+ '--box-shadow-inset-medium',
+ '--box-shadow-inset-medium-vertical',
+ '--box-shadow-outset-small',
+ '--box-shadow-outset-small-top',
+ '--box-shadow-outset-small-bottom',
+ '--box-shadow-outset-small-start',
+ '--box-shadow-outset-medium-below',
+ '--box-shadow-outset-medium-around',
+ '--box-shadow-outset-large-below',
+ '--box-shadow-outset-large-around',
+ '--box-shadow-small',
+ '--box-shadow-small-top',
+ '--box-shadow-small-bottom',
+ '--box-shadow-medium',
+ '--box-shadow-large',
+ '--box-shadow-drop-small',
+ '--box-shadow-drop-medium',
+ '--box-shadow-drop-xx-large',
+] as const
+
+const cursorTokenOrder = [
+ '--cursor-base',
+ '--cursor-base--disabled',
+ '--cursor-base--hover',
+ '--cursor-grab',
+ '--cursor-grabbing',
+ '--cursor-help',
+ '--cursor-move',
+ '--cursor-not-allowed',
+ '--cursor-resize-nesw',
+ '--cursor-resize-nwse',
+ '--cursor-text',
+ '--cursor-zoom-in',
+ '--cursor-zoom-out',
+] as const
+
+const opacityTokenOrder = [
+ '--opacity-base',
+ '--opacity-medium',
+ '--opacity-low',
+ '--opacity-transparent',
+ '--opacity-icon-base',
+ '--opacity-icon-base--hover',
+ '--opacity-icon-base--selected',
+ '--opacity-icon-base--disabled',
+ '--opacity-icon-placeholder',
+ '--opacity-icon-subtle',
+] as const
+
+const outlineTokenOrder = [
+ '--outline-base--focus',
+ '--outline-color-progressive--focus',
+] as const
+
+const transitionTokenOrder = [
+ '--transition-property-base',
+ '--transition-property-fade',
+ '--transition-property-icon',
+ '--transition-property-icon-css-only',
+ '--transition-property-toast',
+ '--transition-property-toggle-switch-grip',
+ '--transition-timing-function-system',
+ '--transition-timing-function-user',
+ '--transition-duration-base',
+ '--transition-duration-medium',
+] as const
+
+const animationTokenOrder = [
+ '--animation-timing-function-base',
+ '--animation-timing-function-bouncing',
+ '--animation-iteration-count-base',
+ '--animation-duration-fast',
+ '--animation-duration-medium',
+ '--animation-duration-slow',
+ '--animation-delay-none',
+ '--animation-delay-medium',
+ '--animation-delay-slow',
+ '--transform-checkbox-tick--checked',
+ '--transform-progress-indicator-spinner-start',
+ '--transform-progress-indicator-spinner-end',
+] as const
+
+const positionTokenOrder = [
+ '--position-offset-border-width-base',
+] as const
+
+const breakpointTokenOrder = [
+ '--min-width-breakpoint-mobile',
+ '--min-width-breakpoint-tablet',
+ '--min-width-breakpoint-desktop',
+ '--min-width-breakpoint-desktop-wide',
+ '--max-width-breakpoint-mobile',
+ '--max-width-breakpoint-tablet',
+ '--max-width-breakpoint-desktop',
+] as const
+
+export function inferTokenSubTab(name: string): TokenSubTab | null {
+ if (name.startsWith('--spacing-')) return 'spacing'
+ if (name.startsWith('--box-sizing-')) return 'box-sizing'
+ if (name.startsWith('--z-index-')) return 'z-index'
+ if (name.includes('breakpoint')) return 'breakpoint'
+ if (name.startsWith('--background-position-') || name.startsWith('--background-size-')) return 'size'
+ if (name.startsWith('--position-')) return 'position'
+
+ if (
+ name.startsWith('--size-') ||
+ name.startsWith('--min-size-') ||
+ name.startsWith('--max-size-') ||
+ name.startsWith('--min-width-') ||
+ name.startsWith('--max-width-') ||
+ name.startsWith('--width-') ||
+ name.startsWith('--height-') ||
+ name.startsWith('--min-height-') ||
+ name.startsWith('--max-height-') ||
+ name.startsWith('--background-size-') ||
+ name.startsWith('--tab-size-')
+ ) {
+ return 'size'
+ }
+
+ if (
+ name.startsWith('--border-radius-') ||
+ name.startsWith('--border-width-') ||
+ name.startsWith('--border-style-') ||
+ borderShorthandNames.has(name)
+ ) {
+ return 'border'
+ }
+
+ if (name.startsWith('--box-shadow-')) return 'box-shadow'
+ if (name.startsWith('--cursor-')) return 'cursor'
+ if (name.startsWith('--opacity-')) return 'opacity'
+ if (name.startsWith('--outline-')) return 'outline'
+
+ if (name.startsWith('--transition-')) return 'transition'
+ if (name.startsWith('--animation-') || name.startsWith('--transform-')) return 'animation'
+
+ return null
+}
+
+export function inferTokenSection(subTab: TokenSubTab): TokenSection {
+ if (layoutSubTabOrder.includes(subTab as LayoutSubTab)) return 'layout'
+ if (appearanceSubTabOrder.includes(subTab as AppearanceSubTab)) return 'appearance'
+ return 'animation'
+}
+
+export function getGeneralTokens(tokens: TokenEntry[]): TokenEntry[] {
+ return tokens.filter((token) => token.family !== 'Color' && token.family !== 'Typography')
+}
+
+const spacingTokenOrder = [
+ '--spacing-0',
+ '--spacing-6',
+ '--spacing-12',
+ '--spacing-25',
+ '--spacing-30',
+ '--spacing-35',
+ '--spacing-50',
+ '--spacing-65',
+ '--spacing-75',
+ '--spacing-100',
+ '--spacing-125',
+ '--spacing-150',
+ '--spacing-200',
+ '--spacing-250',
+ '--spacing-300',
+ '--spacing-400',
+ '--spacing-half',
+ '--spacing-full',
+ '--spacing-horizontal-button',
+ '--spacing-horizontal-button-large',
+ '--spacing-horizontal-button-small',
+ '--spacing-horizontal-button-icon-only',
+ '--spacing-horizontal-button-small-icon-only',
+ '--spacing-horizontal-input-text-two-end-icons',
+ '--spacing-start-typeahead-search-figure',
+ '--spacing-start-typeahead-search-icon',
+ '--spacing-typeahead-search-focus-addition',
+ '--spacing-toggle-switch-grip-start',
+ '--spacing-toggle-switch-grip-end',
+] as const
+
+const sizeTokenOrder = [
+ '--size-0',
+ '--size-6',
+ '--size-12',
+ '--size-25',
+ '--size-50',
+ '--size-75',
+ '--size-100',
+ '--size-125',
+ '--size-150',
+ '--size-200',
+ '--size-250',
+ '--size-275',
+ '--size-300',
+ '--size-400',
+ '--size-800',
+ '--size-1200',
+ '--size-1600',
+ '--size-2400',
+ '--size-2800',
+ '--size-3200',
+ '--size-4000',
+ '--size-5600',
+ '--size-absolute-1',
+ '--size-absolute-9999',
+ '--size-viewport-width-full',
+ '--size-viewport-height-full',
+ '--size-content-min',
+ '--size-content-fit',
+ '--size-content-max',
+ '--size-third',
+ '--size-half',
+ '--size-full',
+ '--size-double',
+ '--size-search-figure',
+ '--size-icon-x-small',
+ '--size-icon-small',
+ '--size-icon-medium',
+ '--size-toggle-switch-grip',
+ '--min-size-interactive-pointer',
+ '--min-size-interactive-touch',
+ '--min-size-icon-x-small',
+ '--min-size-icon-small',
+ '--min-size-icon-medium',
+ '--min-size-search-figure',
+ '--min-size-input-binary',
+ '--min-size-input-chip-clear-button',
+ '--min-size-toggle-switch-grip',
+ '--min-width-medium',
+ '--min-width-breakpoint-mobile',
+ '--min-width-breakpoint-tablet',
+ '--min-width-breakpoint-desktop',
+ '--min-width-breakpoint-desktop-wide',
+ '--min-width-toggle-switch',
+ '--max-width-base',
+ '--max-width-breakpoint-mobile',
+ '--max-width-breakpoint-tablet',
+ '--max-width-breakpoint-desktop',
+ '--max-width-button',
+ '--min-height-text-area',
+ '--min-height-table-header',
+ '--min-height-table-footer',
+ '--min-height-toggle-switch',
+ '--max-height-chip',
+ '--width-toggle-switch',
+ '--height-toggle-switch',
+ '--background-position-base',
+ '--background-size-search-figure',
+ '--tab-size-base',
+] as const
+
+function manualTokenSortIndex(name: string, order: readonly string[]): number {
+ const index = order.indexOf(name)
+ return index === -1 ? order.length : index
+}
+
+export type SpacingTokenGroup = 'Scale' | 'Positioning' | 'Components'
+
+export function getSpacingTokenGroup(name: string): SpacingTokenGroup {
+ if (name === '--spacing-half' || name === '--spacing-full') return 'Positioning'
+ if (/^--spacing-\d/.test(name)) return 'Scale'
+ return 'Components'
+}
+
+export type SizeTokenGroup =
+ | 'Scale'
+ | 'Absolute'
+ | 'Viewport'
+ | 'Content sizing'
+ | 'Fractions'
+ | 'Component sizes'
+ | 'Minimum sizes'
+ | 'Minimum widths'
+ | 'Maximum widths'
+ | 'Minimum heights'
+ | 'Maximum heights'
+ | 'Component dimensions'
+ | 'Other'
+
+export type BreakpointTokenGroup = 'Minimum width' | 'Maximum width'
+
+export function getBreakpointTokenGroup(name: string): BreakpointTokenGroup {
+ return name.startsWith('--max-width-breakpoint-') ? 'Maximum width' : 'Minimum width'
+}
+
+export function getSizeTokenGroup(name: string): SizeTokenGroup {
+ if (/^--size-\d/.test(name)) return 'Scale'
+ if (name.startsWith('--size-absolute-')) return 'Absolute'
+ if (name.startsWith('--size-viewport-')) return 'Viewport'
+ if (name.startsWith('--size-content-')) return 'Content sizing'
+ if (
+ name === '--size-third' ||
+ name === '--size-half' ||
+ name === '--size-full' ||
+ name === '--size-double'
+ ) {
+ return 'Fractions'
+ }
+ if (
+ name.startsWith('--size-search-') ||
+ name.startsWith('--size-icon-') ||
+ name.startsWith('--size-toggle-')
+ ) {
+ return 'Component sizes'
+ }
+ if (name.startsWith('--min-size-')) return 'Minimum sizes'
+ if (name.startsWith('--min-width-')) return 'Minimum widths'
+ if (name.startsWith('--max-width-')) return 'Maximum widths'
+ if (name.startsWith('--min-height-')) return 'Minimum heights'
+ if (name.startsWith('--max-height-')) return 'Maximum heights'
+ if (name.startsWith('--width-') || name.startsWith('--height-')) return 'Component dimensions'
+ if (name.startsWith('--background-position-') || name.startsWith('--background-size-')) return 'Background'
+ return 'Other'
+}
+
+export function isBackgroundPositionToken(name: string): boolean {
+ return name.startsWith('--background-position-')
+}
+
+export function isBackgroundSizeToken(name: string): boolean {
+ return name.startsWith('--background-size-')
+}
+
+export type ZIndexTokenGroup = 'Layout' | 'Stacking'
+
+export function getZIndexTokenGroup(name: string): ZIndexTokenGroup {
+ return name.startsWith('--z-index-stacking-') ? 'Stacking' : 'Layout'
+}
+
+export function parseZIndexValue(value: string): number {
+ return Number.parseInt(value.trim(), 10)
+}
+
+export function usesDimensionBarDemo(token: TokenEntry): boolean {
+ const { name, value } = token
+ if (value === 'none' || value === 'cover') return false
+ if (name.startsWith('--size-content-')) return false
+ if (name === '--tab-size-base') return false
+ return true
+}
+
+export function parsePlainPercentage(value: string): number | null {
+ const match = value.trim().match(/^([\d.]+)%$/)
+ return match ? parseFloat(match[1]) : null
+}
+
+export function isZeroDimensionToken(token: TokenEntry): boolean {
+ const value = token.value.trim()
+ if (value === '0') return true
+ if (/^0(?:px|rem|em|%|vw|vh|ch|ex)?$/i.test(value)) return true
+ return parsePlainPercentage(value) === 0
+}
+
+export function getTokensForTokenSubTab(
+ tokens: TokenEntry[],
+ section: TokenSection,
+ subTab: TokenSubTab,
+): TokenEntry[] {
+ const filtered = getGeneralTokens(tokens).filter((token) => {
+ const tokenSubTab = inferTokenSubTab(token.name)
+ return tokenSubTab === subTab && inferTokenSection(tokenSubTab) === section
+ })
+
+ if (subTab === 'spacing') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, spacingTokenOrder) - manualTokenSortIndex(b.name, spacingTokenOrder),
+ )
+ }
+
+ if (subTab === 'size') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, sizeTokenOrder) - manualTokenSortIndex(b.name, sizeTokenOrder),
+ )
+ }
+
+ if (subTab === 'position') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, positionTokenOrder) - manualTokenSortIndex(b.name, positionTokenOrder),
+ )
+ }
+
+ if (subTab === 'breakpoint') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, breakpointTokenOrder) - manualTokenSortIndex(b.name, breakpointTokenOrder),
+ )
+ }
+
+ if (subTab === 'z-index') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) => {
+ const groupOrder = getZIndexTokenGroup(a.name).localeCompare(getZIndexTokenGroup(b.name))
+ if (groupOrder !== 0) return groupOrder
+ return parseZIndexValue(a.value) - parseZIndexValue(b.value)
+ })
+ }
+
+ if (subTab === 'border') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, borderTokenOrder) - manualTokenSortIndex(b.name, borderTokenOrder),
+ )
+ }
+
+ if (subTab === 'box-shadow') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, boxShadowTokenOrder) - manualTokenSortIndex(b.name, boxShadowTokenOrder),
+ )
+ }
+
+ if (subTab === 'cursor') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, cursorTokenOrder) - manualTokenSortIndex(b.name, cursorTokenOrder),
+ )
+ }
+
+ if (subTab === 'opacity') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, opacityTokenOrder) - manualTokenSortIndex(b.name, opacityTokenOrder),
+ )
+ }
+
+ if (subTab === 'outline') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, outlineTokenOrder) - manualTokenSortIndex(b.name, outlineTokenOrder),
+ )
+ }
+
+ if (subTab === 'transition') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, transitionTokenOrder) - manualTokenSortIndex(b.name, transitionTokenOrder),
+ )
+ }
+
+ if (subTab === 'animation') {
+ return sortTokensWithDeprecatedLast(filtered, (a, b) =>
+ manualTokenSortIndex(a.name, animationTokenOrder) - manualTokenSortIndex(b.name, animationTokenOrder),
+ )
+ }
+
+ return sortTokensWithDeprecatedLast(filtered)
}
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/playground-tabs.ts b/src/prototypes/example-codex-kitchen-sink/lib/playground-tabs.ts
new file mode 100644
index 0000000..0a3c2dc
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/playground-tabs.ts
@@ -0,0 +1,132 @@
+import { buttonsSubTabs, contentDataSubTabs, feedbackSubTabs, formElementsSubTabs, iconsSubTabs, mediaSubTabs, navigationSubTabs, searchSubTabs } from './component-tabs'
+import {
+ colorSubTabs,
+ tokenSectionTabs,
+ typographySubTabs,
+ type TokenSection,
+} from './parse-tokens'
+
+export const mainTabIds = [
+ 'typography',
+ 'color',
+ 'tokens-layout',
+ 'tokens-appearance',
+ 'tokens-animation',
+ 'icons',
+ 'components-buttons',
+ 'components-form-elements',
+ 'components-feedback',
+ 'components-content-data',
+ 'components-media',
+ 'components-navigation',
+ 'components-search',
+] as const
+
+export type MainTabId = (typeof mainTabIds)[number]
+
+export const tokenMainTabBySection: Record = {
+ layout: 'tokens-layout',
+ appearance: 'tokens-appearance',
+ animation: 'tokens-animation',
+}
+
+const subTabIdsByMain: Partial> = {
+ typography: typographySubTabs.map((tab) => tab.id),
+ color: colorSubTabs.map((tab) => tab.id),
+ icons: iconsSubTabs.map((tab) => tab.id),
+ 'components-buttons': buttonsSubTabs.map((tab) => tab.id),
+ 'components-form-elements': formElementsSubTabs.map((tab) => tab.id),
+ 'components-content-data': contentDataSubTabs.map((tab) => tab.id),
+ 'components-feedback': feedbackSubTabs.map((tab) => tab.id),
+ 'components-media': mediaSubTabs.map((tab) => tab.id),
+ 'components-navigation': navigationSubTabs.map((tab) => tab.id),
+ 'components-search': searchSubTabs.map((tab) => tab.id),
+ 'tokens-layout': tokenSectionTabs.find((entry) => entry.id === 'layout')!.subTabs.map((tab) => tab.id),
+ 'tokens-appearance': tokenSectionTabs
+ .find((entry) => entry.id === 'appearance')!
+ .subTabs.map((tab) => tab.id),
+ 'tokens-animation': tokenSectionTabs
+ .find((entry) => entry.id === 'animation')!
+ .subTabs.map((tab) => tab.id),
+}
+
+export const defaultLeafTab = 'typography/style'
+
+function isMainTabId(value: string): value is MainTabId {
+ return (mainTabIds as readonly string[]).includes(value)
+}
+
+export function getDefaultLeafTab(main: MainTabId): string {
+ const subTabs = subTabIdsByMain[main]
+ return subTabs ? `${main}/${subTabs[0]}` : main
+}
+
+export function parseLeafTab(raw: string): { main: MainTabId; sub?: string } | null {
+ const slash = raw.indexOf('/')
+ if (slash === -1) {
+ if (!isMainTabId(raw)) return null
+ if (subTabIdsByMain[raw]) return null
+ return { main: raw }
+ }
+
+ const main = raw.slice(0, slash)
+ const sub = raw.slice(slash + 1)
+ if (!isMainTabId(main)) return null
+
+ const subTabs = subTabIdsByMain[main]
+ if (!subTabs || !subTabs.includes(sub)) return null
+
+ return { main, sub }
+}
+
+export function isValidLeafTab(value: string): boolean {
+ return parseLeafTab(value) !== null
+}
+
+export function resolveLeafTab(raw: string | undefined): string {
+ if (raw && isValidLeafTab(raw)) return raw
+
+ if (raw && isMainTabId(raw) && subTabIdsByMain[raw]) {
+ return getDefaultLeafTab(raw)
+ }
+
+ return defaultLeafTab
+}
+
+export function getSubTabForMain(
+ mainTabId: MainTabId,
+ subTabMemory: Partial>,
+): string {
+ const remembered = subTabMemory[mainTabId]
+ const subTabs = subTabIdsByMain[mainTabId]
+ if (remembered && subTabs?.includes(remembered)) return remembered
+ return subTabs?.[0] ?? ''
+}
+
+export function getRememberedLeafTab(
+ main: MainTabId,
+ subTabMemory: Partial>,
+): string {
+ const subTabs = subTabIdsByMain[main]
+ return subTabs ? `${main}/${getSubTabForMain(main, subTabMemory)}` : main
+}
+
+export function subTabForMain(
+ leafTab: string,
+ mainTabId: MainTabId,
+ subTabMemory: Partial> = {},
+): string {
+ const parsed = parseLeafTab(leafTab)
+ if (parsed?.main === mainTabId && parsed.sub) return parsed.sub
+ return getSubTabForMain(mainTabId, subTabMemory)
+}
+
+export function rememberSubTabForMain(
+ subTabMemory: Partial>,
+ mainTabId: MainTabId,
+ subTabId: string,
+): void {
+ if (subTabIdsByMain[mainTabId]?.includes(subTabId)) {
+ subTabMemory[mainTabId] = subTabId
+ }
+}
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/use-playground-leaf-tab.ts b/src/prototypes/example-codex-kitchen-sink/lib/use-playground-leaf-tab.ts
new file mode 100644
index 0000000..de57425
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/use-playground-leaf-tab.ts
@@ -0,0 +1,112 @@
+import { inject, provide, ref, watch, type InjectionKey, type Ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+
+import {
+ defaultLeafTab,
+ getRememberedLeafTab,
+ parseLeafTab,
+ rememberSubTabForMain,
+ resolveLeafTab,
+ subTabForMain,
+ type MainTabId,
+} from './playground-tabs'
+import { readQueryValue } from './use-url-query-param'
+
+export interface PlaygroundLeafTabContext {
+ leafTab: Ref
+ subTabFor: (mainTabId: MainTabId) => string
+ setSubTab: (mainTabId: MainTabId, subTabId: string) => void
+}
+
+export const playgroundLeafTabKey: InjectionKey = Symbol('playgroundLeafTab')
+
+export function usePlaygroundLeafTab(): Ref {
+ const route = useRoute()
+ const router = useRouter()
+
+ function read(): string {
+ return resolveLeafTab(readQueryValue(route.query.tab))
+ }
+
+ const leafTab = ref(read())
+ let syncingFromRoute = false
+
+ watch(
+ () => route.query.tab,
+ () => {
+ const next = read()
+ if (next === leafTab.value) return
+ syncingFromRoute = true
+ leafTab.value = next
+ syncingFromRoute = false
+ },
+ )
+
+ watch(leafTab, (value) => {
+ if (syncingFromRoute) return
+
+ const normalized = value === defaultLeafTab ? undefined : value
+ const current = readQueryValue(route.query.tab)
+ if (current === normalized) return
+
+ const query = { ...route.query }
+ if (normalized === undefined) {
+ delete query.tab
+ } else {
+ query.tab = normalized
+ }
+ void router.replace({ query })
+ })
+
+ return leafTab
+}
+
+export function providePlaygroundLeafTab(
+ leafTab: Ref,
+): PlaygroundLeafTabContext & { subTabMemory: Partial> } {
+ const subTabMemory: Partial> = {}
+
+ function rememberFromPath(path: string): void {
+ const parsed = parseLeafTab(path)
+ if (parsed?.sub) rememberSubTabForMain(subTabMemory, parsed.main, parsed.sub)
+ }
+
+ rememberFromPath(leafTab.value)
+
+ watch(leafTab, rememberFromPath)
+
+ const context: PlaygroundLeafTabContext = {
+ leafTab,
+ subTabFor: (mainTabId) => subTabForMain(leafTab.value, mainTabId, subTabMemory),
+ setSubTab: (mainTabId, subTabId) => {
+ rememberSubTabForMain(subTabMemory, mainTabId, subTabId)
+ leafTab.value = `${mainTabId}/${subTabId}`
+ },
+ }
+
+ provide(playgroundLeafTabKey, context)
+ return { ...context, subTabMemory }
+}
+
+export function usePlaygroundLeafTabContext(): PlaygroundLeafTabContext | undefined {
+ return inject(playgroundLeafTabKey)
+}
+
+export function syncMainTabWithLeafTab(
+ activeTab: Ref,
+ leafTab: Ref,
+ subTabMemory: Partial>,
+): void {
+ watch(leafTab, (path) => {
+ const parsed = parseLeafTab(path)
+ if (parsed && parsed.main !== activeTab.value) {
+ activeTab.value = parsed.main
+ }
+ })
+
+ watch(activeTab, (main) => {
+ const parsed = parseLeafTab(leafTab.value)
+ if (parsed?.main === main) return
+ leafTab.value = getRememberedLeafTab(main, subTabMemory)
+ })
+}
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts b/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
new file mode 100644
index 0000000..607505c
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/lib/use-url-query-param.ts
@@ -0,0 +1,57 @@
+import { ref, watch, type Ref } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+
+function readQueryValue(raw: unknown): string | undefined {
+ if (typeof raw === 'string') return raw
+ if (Array.isArray(raw) && typeof raw[0] === 'string') return raw[0]
+ return undefined
+}
+
+export { readQueryValue }
+
+export function useUrlQueryParam(
+ key: string,
+ defaultValue: T,
+ isValid: (value: string) => value is T,
+): Ref {
+ const route = useRoute()
+ const router = useRouter()
+
+ function parse(): T {
+ const value = readQueryValue(route.query[key])
+ if (value && isValid(value)) return value
+ return defaultValue
+ }
+
+ const param = ref(parse()) as Ref
+ let syncingFromRoute = false
+
+ watch(
+ () => route.query[key],
+ () => {
+ const next = parse()
+ if (next === param.value) return
+ syncingFromRoute = true
+ param.value = next
+ syncingFromRoute = false
+ },
+ )
+
+ watch(param, (value) => {
+ if (syncingFromRoute) return
+
+ const normalized = value === defaultValue ? undefined : value
+ const current = readQueryValue(route.query[key])
+ if (current === normalized) return
+
+ const query = { ...route.query }
+ if (normalized === undefined) {
+ delete query[key]
+ } else {
+ query[key] = normalized
+ }
+ void router.replace({ query })
+ })
+
+ return param
+}
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue
new file mode 100644
index 0000000..9d71969
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue
@@ -0,0 +1,368 @@
+
+
+
+
+
+ -
+ {{ entry.label }}
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+ {{ previewHint(previewKind(entry.token)) }}
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue
new file mode 100644
index 0000000..516b190
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue
@@ -0,0 +1,173 @@
+
+
+
+
+
+ -
+ {{ entry.label }}
+
+ -
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/BoxShadowTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/BoxShadowTokenList.vue
new file mode 100644
index 0000000..6f0b2e4
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/BoxShadowTokenList.vue
@@ -0,0 +1,243 @@
+
+
+
+
+
+ -
+ {{ entry.label }}
+
+ -
+
+
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/ColorPaletteList.vue b/src/prototypes/example-codex-kitchen-sink/playground/ColorPaletteList.vue
new file mode 100644
index 0000000..99ccc85
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/ColorPaletteList.vue
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/ColorTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/ColorTokenList.vue
new file mode 100644
index 0000000..a5be698
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/ColorTokenList.vue
@@ -0,0 +1,220 @@
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/CursorTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/CursorTokenList.vue
new file mode 100644
index 0000000..747ac5e
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/CursorTokenList.vue
@@ -0,0 +1,94 @@
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue
new file mode 100644
index 0000000..c705160
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue
@@ -0,0 +1,382 @@
+
+
+
+
+
+ -
+ {{ entry.label }}
+
+ -
+
+
+
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
+
+
+
+
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
+
+
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/FontFamilyList.vue b/src/prototypes/example-codex-kitchen-sink/playground/FontFamilyList.vue
new file mode 100644
index 0000000..a34ce5c
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/FontFamilyList.vue
@@ -0,0 +1,48 @@
+
+
+
+
+ -
+ {{ token.name }}
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/FontSizeList.vue b/src/prototypes/example-codex-kitchen-sink/playground/FontSizeList.vue
new file mode 100644
index 0000000..38a466a
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/FontSizeList.vue
@@ -0,0 +1,48 @@
+
+
+
+
+ -
+ {{ token.name }}
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/FontWeightList.vue b/src/prototypes/example-codex-kitchen-sink/playground/FontWeightList.vue
new file mode 100644
index 0000000..e51b4dc
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/FontWeightList.vue
@@ -0,0 +1,49 @@
+
+
+
+
+ -
+ {{ token.name }}
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/LineHeightList.vue b/src/prototypes/example-codex-kitchen-sink/playground/LineHeightList.vue
new file mode 100644
index 0000000..82fd3be
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/LineHeightList.vue
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+ Line one
+ Line two
+ Line three
+
+
+ {{ token.name }}
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/OpacityTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/OpacityTokenList.vue
new file mode 100644
index 0000000..344f5e1
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/OpacityTokenList.vue
@@ -0,0 +1,175 @@
+
+
+
+
+
+ -
+ {{ entry.label }}
+
+ -
+
+
+
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/OutlineTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/OutlineTokenList.vue
new file mode 100644
index 0000000..f75ccf5
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/OutlineTokenList.vue
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue
index 85ecc6d..b8b3283 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundCell.vue
@@ -1,13 +1,14 @@
-
+
@@ -23,6 +24,29 @@ defineProps
()
min-width: 0;
}
+.playground-cell--row {
+ flex-direction: row;
+ align-items: flex-start;
+ gap: var(--spacing-100);
+ padding-block: var(--spacing-35);
+ border-bottom: var(--border-subtle);
+}
+
+.playground-cell--row:last-child {
+ border-bottom: none;
+}
+
+.playground-cell--row .playground-cell__label {
+ flex-shrink: 0;
+ min-width: 5rem;
+ padding-top: var(--spacing-35);
+}
+
+.playground-cell--row .playground-cell__content {
+ flex: 1;
+ max-width: 20rem;
+}
+
.playground-cell__content {
min-width: 0;
}
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundList.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundList.vue
new file mode 100644
index 0000000..a8eec57
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundList.vue
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
index 6fc75eb..15b6558 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSection.vue
@@ -26,7 +26,7 @@ withDefaults(defineProps(), {
}
.playground-section--untitled {
- padding-top: var(--spacing-100);
+ padding: var(--spacing-100) 0;
}
/* .playground-section__title {
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue
index 7c53e43..9db451f 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PlaygroundSubTabs.vue
@@ -1,7 +1,10 @@
@@ -30,7 +39,7 @@ const active = ref(props.defaultActive)
.playground-sub-tabs :deep(> .cdx-tabs__header) {
position: sticky;
top: 35px;
- z-index: 1;
+ z-index: 2;
}
.playground-sub-tabs__panel {
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/PositionTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/PositionTokenList.vue
new file mode 100644
index 0000000..d369239
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/PositionTokenList.vue
@@ -0,0 +1,122 @@
+
+
+
+
+ -
+
+ Component
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/TextDecorationList.vue b/src/prototypes/example-codex-kitchen-sink/playground/TextDecorationList.vue
new file mode 100644
index 0000000..fa2bbc4
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/TextDecorationList.vue
@@ -0,0 +1,49 @@
+
+
+
+
+ -
+ {{ token.name }}
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/TextOverflowList.vue b/src/prototypes/example-codex-kitchen-sink/playground/TextOverflowList.vue
new file mode 100644
index 0000000..dff5b6a
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/TextOverflowList.vue
@@ -0,0 +1,77 @@
+
+
+
+
+
+
+
+ {{ sampleText }}
+
+
+
+ {{ token.name }}
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/TokenDeprecatedLabel.vue b/src/prototypes/example-codex-kitchen-sink/playground/TokenDeprecatedLabel.vue
new file mode 100644
index 0000000..8ecaf2e
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/TokenDeprecatedLabel.vue
@@ -0,0 +1,14 @@
+
+ Deprecated
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue b/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
index 6a8599c..dec6e47 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/TokenSwatch.vue
@@ -1,6 +1,7 @@
+
+
+
+
+ -
+ {{ entry.label }}
+
+ -
+
+ 0
+ 1
+
+ {{ parseZIndexValue(entry.token.value) }}
+
+
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue
index e538dd0..4c63ea4 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/ButtonsSection.vue
@@ -4,56 +4,45 @@ import {
CdxButton,
CdxButtonGroup,
CdxIcon,
- CdxImage,
- CdxInfoChip,
- CdxThumbnail,
+ CdxMenuButton,
CdxToggleButton,
CdxToggleButtonGroup,
- CdxToggleSwitch,
} from '@wikimedia/codex'
-import {
- cdxIconAdd,
- cdxIconAlert,
- cdxIconEdit,
- cdxIconImage,
- cdxIconSearch,
-} from '@wikimedia/codex-icons'
+import { cdxIconAdd, cdxIconEdit, cdxIconEllipsis, cdxIconSearch } from '@wikimedia/codex-icons'
import type { ButtonAction, ButtonSize, ButtonWeight } from '@wikimedia/codex'
+import { buttonsSubTabs } from '../lib/component-tabs'
import {
buttonGroupItems,
buttonGroupLongItems,
- imageUrl,
- thumbnailUrl,
+ menuItems,
toggleGroupItems,
} from '../lib/fixtures'
import PlaygroundSection from '../playground/PlaygroundSection.vue'
import PlaygroundGrid from '../playground/PlaygroundGrid.vue'
import PlaygroundCell from '../playground/PlaygroundCell.vue'
+import PlaygroundSubTabs from '../playground/PlaygroundSubTabs.vue'
const actions: ButtonAction[] = ['default', 'progressive', 'destructive']
const weights: ButtonWeight[] = ['normal', 'primary', 'quiet']
const sizes: ButtonSize[] = ['small', 'medium', 'large']
-const infoChipStatuses = ['notice', 'warning', 'error', 'success'] as const
-const aspectRatios = ['16:9', '3:2', '4:3', '1:1', '3:4', '2:3'] as const
-
-const buttonVariants = computed(() => {
- const variants: {
- action: ButtonAction
- weight: ButtonWeight
- size: ButtonSize
- label: string
- }[] = []
+
+const actionLabels: Record = {
+ default: 'Default',
+ progressive: 'Progressive',
+ destructive: 'Destructive',
+}
+
+const disabledVariants = computed(() => {
+ const variants: { action: ButtonAction; weight: ButtonWeight; label: string }[] = []
for (const action of actions) {
for (const weight of weights) {
- for (const size of sizes) {
- variants.push({
- action,
- weight,
- size,
- label: `${action} / ${weight} / ${size}`,
- })
- }
+ if (weight === 'quiet') continue
+ variants.push({
+ action,
+ weight,
+ label: `${action} / ${weight}`,
+ })
}
}
return variants
@@ -62,161 +51,209 @@ const buttonVariants = computed(() => {
const toggleBold = ref(false)
const toggleBoldDisabled = ref(true)
const toggleAlignment = ref('center')
-const toggleSwitch = ref(true)
-const toggleSwitchDisabled = ref(false)
+const menuButtonSelected = ref(null)
-
-
-
-
- Label
-
-
-
-
- Label
-
-
-
-
-
- Add
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Off
-
-
- On
-
-
- Disabled
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Status
-
-
- Status
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue
index 618b136..1a9dcca 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/ColorSection.vue
@@ -1,32 +1,31 @@
-
-
+
+
-
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/ContentDataSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/ContentDataSection.vue
new file mode 100644
index 0000000..1d2ea4f
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/ContentDataSection.vue
@@ -0,0 +1,261 @@
+
+
+
+
+
+
+
+
+
+ Title
+ Description
+ Body content
+
+
+
+
+ Title
+ Body content
+
+
+
+
+
+
+
+
+
+ Title
+ Description
+
+
+
+
+ Title
+ Description
+
+
+
+
+ Title
+
+
+
+
+ Title
+
+
+
+
+
+
+
+
+ Open
+
+
+ Open
+
+
+ Open
+
+
+ Open
+
+
+
+
+ Body
+
+
+
+ Body
+
+
+
+ Body
+
+ Cancel
+
+ Add
+
+
+
+
+
+ Body
+
+
+
+
+
+
+
+
+
+
+ Pending
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Toggle
+
+ Body
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Hover
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue
index 6e697e8..ea2dade 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/FeedbackSection.vue
@@ -1,16 +1,20 @@
-
-
-
- Message text
-
-
- Message text
-
-
-
- Message text
-
-
-
-
+
+
+
+
+ Notice
+ Warning
+ Error
+ Success
+ No icon
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+ Message text
+
+
+ Message text
+
+
+
+ Message text
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
- Loading
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
- Show
-
-
-
+
+
+
+
+
+
+
+ Loading
+
+
+
+
+
+ Loading
+
+
+
+
+
+
+
+
+ Show
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/FormElementsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/FormElementsSection.vue
new file mode 100644
index 0000000..634ec67
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/FormElementsSection.vue
@@ -0,0 +1,268 @@
+
+
+
+
+
+
+
+
+ Label
+
+
+ Label
+
+
+ Label
+
+
+ A
+ B
+ C
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Label
+ Description
+
+ Help text
+
+
+
+
+
+
+
+
+ Label
+
+
+ Label
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Published
+ Draft
+
+
+ Draft
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Off
+
+
+ On
+
+
+ Disabled
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
index 574b693..b8c2c32 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
@@ -5,33 +5,99 @@ import * as codexIcons from '@wikimedia/codex-icons'
import type { IconSize } from '@wikimedia/codex'
import { cdxIconSearch } from '@wikimedia/codex-icons'
+import { iconSizeEntries, iconsSubTabs } from '../lib/component-tabs'
import PlaygroundSection from '../playground/PlaygroundSection.vue'
-import PlaygroundGrid from '../playground/PlaygroundGrid.vue'
-import PlaygroundCell from '../playground/PlaygroundCell.vue'
+import PlaygroundSubTabs from '../playground/PlaygroundSubTabs.vue'
+import TokenDeprecatedLabel from '../playground/TokenDeprecatedLabel.vue'
const iconEntries = computed(() =>
Object.entries(codexIcons)
.filter(([name]) => name.startsWith('cdxIcon'))
.sort(([a], [b]) => a.localeCompare(b)),
)
-
-const iconSizes: IconSize[] = ['xx-small', 'x-small', 'small', 'medium']
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ {{ entry.id }}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue
deleted file mode 100644
index 07c4f0d..0000000
--- a/src/prototypes/example-codex-kitchen-sink/sections/InputsSection.vue
+++ /dev/null
@@ -1,283 +0,0 @@
-
-
-
-
-
-
-
- Label
- Description
-
- Help text
-
-
-
-
-
-
-
-
- Label text
-
-
- Optional label
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Checkbox
-
-
- Checked
-
-
- Disabled
-
-
- A
- B
- C
-
-
-
-
-
-
-
- Published
- Draft
-
-
- Draft
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Title
- Description
- Body content
-
-
-
-
- Title
- Body content
-
-
-
-
-
-
-
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue
deleted file mode 100644
index 2e7c517..0000000
--- a/src/prototypes/example-codex-kitchen-sink/sections/LayoutSection.vue
+++ /dev/null
@@ -1,136 +0,0 @@
-
-
-
-
-
-
-
- Title
- Description
-
-
-
-
- Title
- Description
-
-
-
-
- Title
-
-
-
-
- Title
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Article
-
-
- Talk
-
-
- History
-
-
-
-
-
-
- Article
-
-
- Talk
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/MediaSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/MediaSection.vue
new file mode 100644
index 0000000..b3764b4
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/MediaSection.vue
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/NavigationSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/NavigationSection.vue
new file mode 100644
index 0000000..8863eb1
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/NavigationSection.vue
@@ -0,0 +1,136 @@
+
+
+
+
+
+
+
+
+
+ The cat (Felis catus) is a
+ domestic species
+ of small
+ carnivorous mammal.
+
+
+
+
+ As a
+ predator,
+ it is
+ crepuscular.
+
+
+
+
+ According to
+
+ "Living with a Cat"
+
+ ,
+ cats are ready to go to new homes at about 12 weeks of age.
+
+
+
+
+ Websites for cat lovers include
+ The Catnip Times
+ and
+ Vanggy.
+
+
+
+
+
+
+
+
+
+
+ Article
+
+
+ Talk
+
+
+ History
+
+
+
+
+
+
+ Article
+
+
+ Talk
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue
deleted file mode 100644
index ae65791..0000000
--- a/src/prototypes/example-codex-kitchen-sink/sections/OverlaysSection.vue
+++ /dev/null
@@ -1,184 +0,0 @@
-
-
-
-
-
-
- Open
-
-
- Open
-
-
- Open
-
-
- Open
-
-
-
-
- Body
-
-
-
- Body
-
-
-
- Body
-
- Cancel
-
- Add
-
-
-
-
-
- Body
-
-
-
-
-
-
- Hover
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Pending
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Toggle
-
- Body
-
-
-
-
-
-
-
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/SearchSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/SearchSection.vue
new file mode 100644
index 0000000..b5d08cd
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/SearchSection.vue
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Search Wikipedia for pages containing
+ {{ searchQuery }}
+
+
+
+
+
+
+ Search Wikipedia for pages containing
+ {{ searchQuery }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue
new file mode 100644
index 0000000..8b1425f
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
deleted file mode 100644
index 16fd818..0000000
--- a/src/prototypes/example-codex-kitchen-sink/sections/TokensSection.vue
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
index bb1f1c8..87f18e4 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TypographySection.vue
@@ -5,17 +5,25 @@ import {
parseTokensFromCss,
typographySubTabs,
} from '../lib/parse-tokens'
-import type { TypographySubTab } from '../lib/parse-tokens'
+import FontFamilyList from '../playground/FontFamilyList.vue'
+import FontSizeList from '../playground/FontSizeList.vue'
+import FontWeightList from '../playground/FontWeightList.vue'
+import LineHeightList from '../playground/LineHeightList.vue'
import PlaygroundSection from '../playground/PlaygroundSection.vue'
-import PlaygroundGrid from '../playground/PlaygroundGrid.vue'
import PlaygroundSubTabs from '../playground/PlaygroundSubTabs.vue'
-import TokenSwatch from '../playground/TokenSwatch.vue'
+import TextDecorationList from '../playground/TextDecorationList.vue'
+import TextOverflowList from '../playground/TextOverflowList.vue'
const allTokens = parseTokensFromCss(tokensCss)
-
+
Heading 1
@@ -25,23 +33,34 @@ const allTokens = parseTokensFromCss(tokensCss)
Body
Small
Cite
- Figure caption
+ Figure caption
Code
Block quote
Pre
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From 4bd93473f63ffc009e2fc75e894d1d2a619af3f1 Mon Sep 17 00:00:00 2001
From: Lu Wilson
Date: Tue, 23 Jun 2026 11:12:32 +0100
Subject: [PATCH 09/10] spacing scroll
---
.../playground/DimensionTokenList.vue | 55 ++++++++++---------
1 file changed, 29 insertions(+), 26 deletions(-)
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue
index c705160..9026774 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/DimensionTokenList.vue
@@ -157,27 +157,31 @@ watch(entries, scheduleOverflowUpdate)
>
-
-
- {{ entry.token.name }}
- {{ entry.token.value }}
-
+ class="dimension-token-list__track"
+ :class="{ 'dimension-token-list__track--zero': isZeroDimensionToken(entry.token) }"
+ :style="trackStyle(entry.token)"
+ >
+
+
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+
+
-
+
Date: Tue, 23 Jun 2026 13:53:48 +0100
Subject: [PATCH 10/10] update
---
.../lib/parse-tokens.ts | 78 ++++---
.../playground/AnimationTokenList.vue | 108 ++++++---
.../playground/BorderTokenList.vue | 117 ++++++++--
.../playground/BoxShadowTokenList.vue | 25 +-
.../playground/CursorTokenList.vue | 51 ++--
.../playground/DimensionTokenList.vue | 19 +-
.../playground/OpacityTokenList.vue | 25 +-
.../playground/PositionTokenList.vue | 122 ----------
.../playground/TokenListGroupHeading.vue | 26 +++
.../playground/ZIndexTokenList.vue | 23 +-
.../sections/IconsSection.vue | 221 +++++++++++++++++-
.../sections/TokenSection.vue | 5 -
12 files changed, 496 insertions(+), 324 deletions(-)
delete mode 100644 src/prototypes/example-codex-kitchen-sink/playground/PositionTokenList.vue
create mode 100644 src/prototypes/example-codex-kitchen-sink/playground/TokenListGroupHeading.vue
diff --git a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
index 6bf3fca..52855da 100644
--- a/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
+++ b/src/prototypes/example-codex-kitchen-sink/lib/parse-tokens.ts
@@ -90,7 +90,12 @@ export function inferTokenFamily(name: string): TokenFamily {
) {
return 'Typography'
}
- if (name.startsWith('--border-radius-') || name.startsWith('--border-width-') || name.startsWith('--border-style-')) {
+ if (
+ name.startsWith('--border-radius-') ||
+ name.startsWith('--border-width-') ||
+ name.startsWith('--border-style-') ||
+ name.startsWith('--position-offset-')
+ ) {
return 'Border'
}
if (name.startsWith('--box-shadow-')) return 'Shadow'
@@ -114,7 +119,12 @@ export function inferTokenCategory(name: string): string {
if (name.startsWith('--border-color-')) return 'Border colors'
if (name.startsWith('--color-')) return 'Text colors'
if (name.startsWith('--spacing-')) return 'Spacing'
- if (name.startsWith('--border-radius-') || name.startsWith('--border-width-') || name.startsWith('--border-style-')) {
+ if (
+ name.startsWith('--border-radius-') ||
+ name.startsWith('--border-width-') ||
+ name.startsWith('--border-style-') ||
+ name.startsWith('--position-offset-')
+ ) {
return 'Border'
}
if (name.startsWith('--font-') || name.startsWith('--line-height-') || name.startsWith('--letter-spacing-')) {
@@ -429,7 +439,6 @@ export type TokenSection = 'layout' | 'appearance' | 'animation'
export type LayoutSubTab =
| 'spacing'
| 'size'
- | 'position'
| 'breakpoint'
| 'z-index'
| 'box-sizing'
@@ -443,7 +452,6 @@ export type TokenSubTab = LayoutSubTab | AppearanceSubTab | AnimationSubTab
const layoutSubTabOrder: LayoutSubTab[] = [
'spacing',
'size',
- 'position',
'breakpoint',
'z-index',
'box-sizing',
@@ -462,7 +470,6 @@ const animationSubTabOrder: AnimationSubTab[] = ['animation', 'transition']
const layoutSubTabLabels: Record
= {
spacing: 'Spacing',
size: 'Size',
- position: 'Position',
breakpoint: 'Breakpoint',
'z-index': 'Z-index',
'box-sizing': 'Box-sizing',
@@ -507,7 +514,11 @@ const borderShorthandNames = new Set([
'--border-transparent',
])
-export type BorderTokenGroup = 'Shorthand' | 'Radius' | 'Style' | 'Width'
+export type BorderTokenGroup = 'Shorthand' | 'Radius' | 'Style' | 'Width' | 'Offset'
+
+export function isPositionOffsetToken(name: string): boolean {
+ return name.startsWith('--position-offset-')
+}
export type TransitionTokenGroup = 'Property' | 'Timing' | 'Duration'
@@ -517,6 +528,7 @@ export function getBorderTokenGroup(name: string): BorderTokenGroup {
if (borderShorthandNames.has(name)) return 'Shorthand'
if (name.startsWith('--border-radius-')) return 'Radius'
if (name.startsWith('--border-style-')) return 'Style'
+ if (isPositionOffsetToken(name)) return 'Offset'
return 'Width'
}
@@ -549,28 +561,31 @@ const borderTokenOrder = [
'--border-width-base',
'--border-width-thick',
'--border-width-input-radio--checked',
+ '--position-offset-border-width-base',
] as const
-export type BoxShadowTokenGroup = 'Color' | 'Inset' | 'Outset' | 'Presets'
+export type BoxShadowTokenGroup = 'Shorthand' | 'Color' | 'Inset' | 'Outset' | 'Deprecated'
+
+const deprecatedBoxShadowTokens = new Set([
+ '--box-shadow-drop-small',
+ '--box-shadow-drop-medium',
+ '--box-shadow-drop-xx-large',
+])
export function getBoxShadowTokenGroup(name: string): BoxShadowTokenGroup {
if (name.startsWith('--box-shadow-color-')) return 'Color'
if (name.startsWith('--box-shadow-inset-')) return 'Inset'
if (name.startsWith('--box-shadow-outset-')) return 'Outset'
- return 'Presets'
+ if (deprecatedBoxShadowTokens.has(name)) return 'Deprecated'
+ return 'Shorthand'
}
const boxShadowTokenOrder = [
- '--box-shadow-color-alpha-base',
- '--box-shadow-color-base',
- '--box-shadow-color-destructive--focus',
- '--box-shadow-color-inverted',
- '--box-shadow-color-progressive--active',
- '--box-shadow-color-progressive--focus',
- '--box-shadow-color-progressive-selected',
- '--box-shadow-color-progressive-selected--active',
- '--box-shadow-color-progressive-selected--hover',
- '--box-shadow-color-transparent',
+ '--box-shadow-small',
+ '--box-shadow-small-top',
+ '--box-shadow-small-bottom',
+ '--box-shadow-medium',
+ '--box-shadow-large',
'--box-shadow-inset-small',
'--box-shadow-inset-medium',
'--box-shadow-inset-medium-vertical',
@@ -582,11 +597,16 @@ const boxShadowTokenOrder = [
'--box-shadow-outset-medium-around',
'--box-shadow-outset-large-below',
'--box-shadow-outset-large-around',
- '--box-shadow-small',
- '--box-shadow-small-top',
- '--box-shadow-small-bottom',
- '--box-shadow-medium',
- '--box-shadow-large',
+ '--box-shadow-color-alpha-base',
+ '--box-shadow-color-base',
+ '--box-shadow-color-destructive--focus',
+ '--box-shadow-color-inverted',
+ '--box-shadow-color-progressive--active',
+ '--box-shadow-color-progressive--focus',
+ '--box-shadow-color-progressive-selected',
+ '--box-shadow-color-progressive-selected--active',
+ '--box-shadow-color-progressive-selected--hover',
+ '--box-shadow-color-transparent',
'--box-shadow-drop-small',
'--box-shadow-drop-medium',
'--box-shadow-drop-xx-large',
@@ -654,10 +674,6 @@ const animationTokenOrder = [
'--transform-progress-indicator-spinner-end',
] as const
-const positionTokenOrder = [
- '--position-offset-border-width-base',
-] as const
-
const breakpointTokenOrder = [
'--min-width-breakpoint-mobile',
'--min-width-breakpoint-tablet',
@@ -674,7 +690,7 @@ export function inferTokenSubTab(name: string): TokenSubTab | null {
if (name.startsWith('--z-index-')) return 'z-index'
if (name.includes('breakpoint')) return 'breakpoint'
if (name.startsWith('--background-position-') || name.startsWith('--background-size-')) return 'size'
- if (name.startsWith('--position-')) return 'position'
+ if (name.startsWith('--position-offset-')) return 'border'
if (
name.startsWith('--size-') ||
@@ -949,12 +965,6 @@ export function getTokensForTokenSubTab(
)
}
- if (subTab === 'position') {
- return sortTokensWithDeprecatedLast(filtered, (a, b) =>
- manualTokenSortIndex(a.name, positionTokenOrder) - manualTokenSortIndex(b.name, positionTokenOrder),
- )
- }
-
if (subTab === 'breakpoint') {
return sortTokensWithDeprecatedLast(filtered, (a, b) =>
manualTokenSortIndex(a.name, breakpointTokenOrder) - manualTokenSortIndex(b.name, breakpointTokenOrder),
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue
index 9d71969..3762cae 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/AnimationTokenList.vue
@@ -6,6 +6,7 @@ import {
getTransitionTokenGroup,
} from '../lib/parse-tokens'
import TokenDeprecatedLabel from './TokenDeprecatedLabel.vue'
+import TokenListGroupHeading from './TokenListGroupHeading.vue'
const props = defineProps<{
kind: 'animation' | 'transition'
@@ -29,6 +30,7 @@ type PreviewKind =
const entries = computed(() => {
const items: ListEntry[] = []
let currentGroup: string | null = null
+ let skipNextGroupHeading = props.kind === 'transition'
for (const token of props.tokens) {
const group =
@@ -37,8 +39,11 @@ const entries = computed(() => {
: getAnimationTokenGroup(token.name)
if (group !== currentGroup) {
- items.push({ type: 'group', label: group })
+ if (!skipNextGroupHeading) {
+ items.push({ type: 'group', label: group })
+ }
currentGroup = group
+ skipNextGroupHeading = false
}
items.push({ type: 'token', token })
@@ -47,6 +52,10 @@ const entries = computed(() => {
return items
})
+function isTransitionPropertyToken(token: TokenEntry): boolean {
+ return props.kind === 'transition' && token.name.startsWith('--transition-property-')
+}
+
function previewKind(token: TokenEntry): PreviewKind {
if (props.kind === 'transition') {
if (token.name.startsWith('--transition-property-')) return 'transition-property'
@@ -62,7 +71,7 @@ function previewKind(token: TokenEntry): PreviewKind {
}
function previewHint(kind: PreviewKind): string | null {
- if (kind === 'transition-property' || kind === 'transition-timing-function' || kind === 'transition-duration') {
+ if (kind === 'transition-timing-function' || kind === 'transition-duration') {
return 'Hover to preview'
}
return null
@@ -72,28 +81,38 @@ function previewHint(kind: PreviewKind): string | null {
- -
- {{ entry.label }}
-
+
-
-
+
+ >
+ {{ entry.token.name }}
+ {{ entry.token.value }}
+ Hover to preview
+
+
+
+
+
+
@@ -197,20 +217,6 @@ function previewHint(kind: PreviewKind): string | null {
list-style: none;
}
-.animation-token-list__group {
- padding: var(--spacing-100) 0 var(--spacing-50);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-bold);
- line-height: var(--line-height-small);
- color: var(--color-subtle);
- text-transform: uppercase;
- letter-spacing: 0.04em;
-}
-
-.animation-token-list__group:first-child {
- padding-top: 0;
-}
-
.animation-token-list__item {
display: grid;
grid-template-columns: 12rem 1fr;
@@ -220,18 +226,47 @@ function previewHint(kind: PreviewKind): string | null {
border-bottom: var(--border-subtle);
}
+.animation-token-list__item--property {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--spacing-25);
+ padding-block: 0;
+ border-bottom: none;
+ margin-top: var(--spacing-75);
+}
+
+.animation-token-list__item--property:first-child {
+ margin-top: 0;
+}
+
+.animation-token-list__property-wrap {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--spacing-25);
+}
+
.animation-token-list__preview {
display: flex;
align-items: center;
min-height: 2.75rem;
}
-.animation-token-list__sample {
- display: block;
- width: 100%;
- min-height: 2.75rem;
+.animation-token-list__sample--property {
+ display: inline-flex;
+ flex-direction: column;
+ align-items: flex-start;
+ gap: var(--spacing-25);
+ width: auto;
+ min-height: 0;
+ padding: var(--spacing-35) var(--spacing-50);
+ font-family: var(--font-family-monospace);
+ font-size: var(--font-size-medium);
+ line-height: var(--line-height-medium);
+ color: var(--color-base);
background: var(--background-color-neutral-subtle);
- border: var(--border-base);
+ border: var(--border-subtle);
border-radius: var(--border-radius-base);
box-shadow: none;
opacity: 1;
@@ -247,6 +282,19 @@ function previewHint(kind: PreviewKind): string | null {
transform: scale(1.02);
}
+.animation-token-list__sample--property .animation-token-list__value,
+.animation-token-list__sample--property .animation-token-list__hint {
+ font-size: var(--font-size-small);
+ line-height: var(--line-height-small);
+ color: var(--color-subtle);
+}
+
+.animation-token-list__item:hover .animation-token-list__sample--property .animation-token-list__value,
+.animation-token-list__item:hover .animation-token-list__sample--property .animation-token-list__hint {
+ color: inherit;
+ opacity: 0.85;
+}
+
.animation-token-list__track {
position: relative;
width: 100%;
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue
index 516b190..373e00c 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/BorderTokenList.vue
@@ -1,8 +1,9 @@
-
-
-
- -
-
- Component
-
-
-
-
-
-
-
-
-
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/TokenListGroupHeading.vue b/src/prototypes/example-codex-kitchen-sink/playground/TokenListGroupHeading.vue
new file mode 100644
index 0000000..f9478f3
--- /dev/null
+++ b/src/prototypes/example-codex-kitchen-sink/playground/TokenListGroupHeading.vue
@@ -0,0 +1,26 @@
+
+
+
+ -
+
{{ label }}
+
+
+
+
diff --git a/src/prototypes/example-codex-kitchen-sink/playground/ZIndexTokenList.vue b/src/prototypes/example-codex-kitchen-sink/playground/ZIndexTokenList.vue
index d02b111..bc97082 100644
--- a/src/prototypes/example-codex-kitchen-sink/playground/ZIndexTokenList.vue
+++ b/src/prototypes/example-codex-kitchen-sink/playground/ZIndexTokenList.vue
@@ -3,14 +3,13 @@ import { computed } from 'vue'
import type { TokenEntry } from '../lib/parse-tokens'
import { getZIndexTokenGroup, parseZIndexValue } from '../lib/parse-tokens'
import TokenDeprecatedLabel from './TokenDeprecatedLabel.vue'
+import TokenListGroupHeading from './TokenListGroupHeading.vue'
const props = defineProps<{
tokens: TokenEntry[]
}>()
-type ListEntry =
- | { type: 'group'; label: string }
- | { type: 'token'; token: TokenEntry }
+type ListEntry = { type: 'group'; label: string } | { type: 'token'; token: TokenEntry }
const entries = computed(() => {
const items: ListEntry[] = []
@@ -36,9 +35,7 @@ function tokenLayerStyle(token: TokenEntry): Record
{
- -
- {{ entry.label }}
-
+
- {
list-style: none;
}
-.z-index-token-list__group {
- padding: var(--spacing-100) 0 var(--spacing-50);
- font-size: var(--font-size-small);
- font-weight: var(--font-weight-bold);
- line-height: var(--line-height-small);
- color: var(--color-subtle);
- text-transform: uppercase;
- letter-spacing: 0.04em;
-}
-
-.z-index-token-list__group:first-child {
- padding-top: 0;
-}
-
.z-index-token-list__item {
display: grid;
grid-template-columns: 6rem 1fr;
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
index b8c2c32..289ceff 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/IconsSection.vue
@@ -1,6 +1,6 @@
@@ -41,10 +208,31 @@ const iconEntries = computed(() =>
-
+
+ Icons only
+
+
-
- {{ name }}
+
+
+
+
+ {{ name }}
+
+
+
+
@@ -77,6 +265,10 @@ const iconEntries = computed(() =>
opacity: 0.7;
}
+.icon-catalogue__toggle {
+ margin-bottom: var(--spacing-100);
+}
+
.icon-catalogue {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(17.5rem, 1fr));
@@ -84,6 +276,12 @@ const iconEntries = computed(() =>
row-gap: var(--spacing-35);
}
+.icon-catalogue--icons-only {
+ grid-template-columns: repeat(auto-fill, minmax(2.75rem, 1fr));
+ column-gap: var(--spacing-50);
+ row-gap: var(--spacing-50);
+}
+
.icon-catalogue__item {
display: flex;
align-items: center;
@@ -91,6 +289,19 @@ const iconEntries = computed(() =>
min-width: 0;
}
+.icon-catalogue--icons-only .icon-catalogue__item {
+ justify-content: center;
+}
+
+.icon-catalogue--icons-only :deep(.cdx-tooltip) {
+ user-select: text;
+}
+
+.icon-catalogue__icon-wrap {
+ display: inline-flex;
+ flex-shrink: 0;
+}
+
.icon-catalogue__name {
font-family: var(--font-family-monospace);
font-size: var(--font-size-small);
diff --git a/src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue b/src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue
index 8b1425f..7d6f63e 100644
--- a/src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue
+++ b/src/prototypes/example-codex-kitchen-sink/sections/TokenSection.vue
@@ -14,7 +14,6 @@ import CursorTokenList from '../playground/CursorTokenList.vue'
import DimensionTokenList from '../playground/DimensionTokenList.vue'
import OpacityTokenList from '../playground/OpacityTokenList.vue'
import OutlineTokenList from '../playground/OutlineTokenList.vue'
-import PositionTokenList from '../playground/PositionTokenList.vue'
import ZIndexTokenList from '../playground/ZIndexTokenList.vue'
import PlaygroundGrid from '../playground/PlaygroundGrid.vue'
import PlaygroundSection from '../playground/PlaygroundSection.vue'
@@ -46,10 +45,6 @@ const sectionConfig = tokenSectionTabs.find((entry) => entry.id === props.sectio
getTokensForTokenSubTab(allTokens, section, subTabId as TokenSubTab)
"
/>
-