From 38e7da5692677c58eda2618d61638c16da46a39c Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Thu, 19 Feb 2026 13:55:29 +0000 Subject: [PATCH 1/5] Remove ESLint config --- .eslintrc.js | 155 --------------------------------------------------- 1 file changed, 155 deletions(-) delete mode 100644 .eslintrc.js diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 57a24fa0..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * @type {ESLint.ConfigData} - */ -module.exports = { - extends: ['prettier'], - ignorePatterns: [ - '**/app/**', - '**/public/**', - - // Enable dotfile linting - '!.*', - 'node_modules', - 'node_modules/.*', - - // Prevent CHANGELOG history changes - 'CHANGELOG.md' - ], - overrides: [ - { - files: ['**/*.{cjs,js,mjs}'], - extends: [ - 'eslint:recommended', - 'plugin:import/recommended', - 'plugin:jest/style', - 'plugin:jest-dom/recommended', - 'plugin:jsdoc/recommended-typescript-flavor', - 'plugin:n/recommended', - 'plugin:promise/recommended', - 'plugin:@typescript-eslint/strict', - 'plugin:@typescript-eslint/stylistic', - 'prettier' - ], - parser: '@typescript-eslint/parser', - parserOptions: { - ecmaVersion: 'latest' - }, - plugins: [ - '@typescript-eslint', - 'import', - 'jsdoc', - 'n', - 'promise', - 'jest', - 'jest-dom' - ], - rules: { - // Always import Node.js packages from `node:*` - 'import/enforce-node-protocol-usage': ['error', 'always'], - - // Check import or require statements are A-Z ordered - 'import/order': [ - 'error', - { - 'alphabetize': { order: 'asc' }, - 'newlines-between': 'always' - } - ], - - // Check for valid formatting - 'jsdoc/check-line-alignment': [ - 'warn', - 'never', - { - wrapIndent: ' ' - } - ], - - // JSDoc blocks are optional by default - 'jsdoc/require-jsdoc': 'off', - - // Require hyphens before param description - // Aligns with TSDoc style: https://tsdoc.org/pages/tags/param/ - 'jsdoc/require-hyphen-before-param-description': 'warn', - - // JSDoc @param required in (optional) blocks but - // @param description is not necessary by default - 'jsdoc/require-param-description': 'off', - 'jsdoc/require-param-type': 'error', - 'jsdoc/require-param': 'off', - - // JSDoc @returns is optional - 'jsdoc/require-returns-description': 'off', - 'jsdoc/require-returns-type': 'off', - 'jsdoc/require-returns': 'off', - - // Maintain new line after description - 'jsdoc/tag-lines': [ - 'warn', - 'never', - { - startLines: 1 - } - ] - }, - settings: { - jsdoc: { - // Allows us to use type declarations that exist in our dependencies - mode: 'typescript' - } - } - }, - { - // CommonJS modules allow require statements - files: ['**/*.{cjs,js}'], - rules: { - '@typescript-eslint/no-require-imports': 'off', - '@typescript-eslint/no-var-requires': 'off' - } - }, - { - // ES modules mandatory file extensions - files: ['**/*.mjs'], - rules: { - 'import/extensions': [ - 'error', - 'always', - { - ignorePackages: true, - pattern: { - cjs: 'always', - js: 'always', - mjs: 'always' - } - } - ] - } - }, - { - // Configure ESLint in test files - files: ['**/*.test.{cjs,js,mjs}'], - extends: ['plugin:jest/recommended', 'plugin:jest/style'], - env: { - 'jest/globals': true - }, - plugins: ['jest'] - }, - { - // Configure ESLint in browser JavaScript - files: ['app/assets/**/*.{cjs,js,mjs}'], - excludedFiles: ['app/assets/**/*.test.{cjs,js,mjs}'], - env: { - browser: true - }, - parserOptions: { - // Note: Allow ES2015 for import/export syntax - ecmaVersion: '2015' - } - } - ], - root: true -} - -/** - * @import { ESLint } from 'eslint' - */ From b9d6ef1e80085f21ffbc88ced67109379acf25da Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Thu, 19 Feb 2026 13:58:08 +0000 Subject: [PATCH 2/5] Enable Stylelint and Prettier --- package-lock.json | 1452 +++++++++++++++++++++++++++++++++++++++++- package.json | 16 +- stylelint.config.mjs | 283 ++++++++ 3 files changed, 1740 insertions(+), 11 deletions(-) create mode 100644 stylelint.config.mjs diff --git a/package-lock.json b/package-lock.json index c1befa36..56892521 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,17 +19,192 @@ "pluralize": "^8.0.0", "weighted": "^1.0.0" }, + "devDependencies": { + "postcss": "^8.5.6", + "postcss-scss": "^4.0.9", + "prettier": "^3.8.1", + "stylelint": "^16.26.1", + "stylelint-config-gds": "^2.0.0", + "stylelint-order": "^7.0.1" + }, "engines": { "node": "^22.16.0 || ^24.11.0", "npm": "^11.6.1" } }, + "node_modules/@babel/code-frame": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", + "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.28.5", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@bufbuild/protobuf": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz", "integrity": "sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==", "license": "(Apache-2.0 AND BSD-3-Clause)" }, + "node_modules/@cacheable/memory": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@cacheable/memory/-/memory-2.0.7.tgz", + "integrity": "sha512-RbxnxAMf89Tp1dLhXMS7ceft/PGsDl1Ip7T20z5nZ+pwIAsQ1p2izPjVG69oCLv/jfQ7HDPHTWK0c9rcAWXN3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/utils": "^2.3.3", + "@keyv/bigmap": "^1.3.0", + "hookified": "^1.14.0", + "keyv": "^5.5.5" + } + }, + "node_modules/@cacheable/utils": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/@cacheable/utils/-/utils-2.3.4.tgz", + "integrity": "sha512-knwKUJEYgIfwShABS1BX6JyJJTglAFcEU7EXqzTdiGCXur4voqkiJkdgZIQtWNFhynzDWERcTYv/sETMu3uJWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.3.0", + "keyv": "^5.6.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz", + "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.0.27", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.0.27.tgz", + "integrity": "sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0" + }, + "node_modules/@csstools/css-tokenizer": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz", + "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@csstools/media-query-list-parser": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-4.0.3.tgz", + "integrity": "sha512-HAYH7d3TLRHDOUQK4mZKf9k9Ph/m8Akstg66ywKR4SFAigjs3yBiUeZtFxywiTm5moZMAp/5W/ZuFnNXXYLuuQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-tokenizer": "^3.0.4" + } + }, + "node_modules/@csstools/selector-specificity": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@csstools/selector-specificity/-/selector-specificity-5.0.0.tgz", + "integrity": "sha512-PCqQV3c4CoVm3kdPhyeZ07VmBRdH2EpMFA/pd9OASpOEC3aXNGoqPDAZ80D0cLpMBxnmk0+yNhGsEx31hq7Gtw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss-selector-parser": "^7.0.0" + } + }, + "node_modules/@dual-bundle/import-meta-resolve": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@dual-bundle/import-meta-resolve/-/import-meta-resolve-4.2.1.tgz", + "integrity": "sha512-id+7YRUgoUX6CgV0DtuhirQWodeeA7Lf4i2x71JS/vtA5pRb/hIGWlw+G6MeXvsM+MXrz0VAydTGElX1rAfgPg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/JounQin" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.27.3", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz", @@ -838,6 +1013,68 @@ } } }, + "node_modules/@keyv/bigmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@keyv/bigmap/-/bigmap-1.3.1.tgz", + "integrity": "sha512-WbzE9sdmQtKy8vrNPa9BRnwZh5UF4s1KTmSK0KUVLo3eff5BlQNNWDnFOouNpKfPKDnms9xynJjsMYjMaT/aFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "hashery": "^1.4.0", + "hookified": "^1.15.0" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "keyv": "^5.6.0" + } + }, + "node_modules/@keyv/serialize": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@keyv/serialize/-/serialize-1.1.1.tgz", + "integrity": "sha512-dXn3FZhPv0US+7dtJsIi2R+c7qWYiReoEh5zUntWCf4oSpMNib8FDhSoed6m3QyZdx5hK7iLFkYk3rNxwt8vTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@parcel/watcher": { "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", @@ -1173,7 +1410,6 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-25.3.0.tgz", "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==", "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -1182,13 +1418,15 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", + "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -1197,6 +1435,23 @@ "node": ">= 0.6" } }, + "node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -1240,11 +1495,32 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "license": "MIT" + "license": "MIT", + "peer": true + }, + "node_modules/astral-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, "node_modules/async": { "version": "2.6.4", @@ -1545,6 +1821,20 @@ "node": ">= 0.8" } }, + "node_modules/cacheable": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/cacheable/-/cacheable-2.3.2.tgz", + "integrity": "sha512-w+ZuRNmex9c1TR9RcsxbfTKCjSL0rh1WA5SABbrWprIHeNBdmyQLSYonlDy9gpD+63XT8DgZ/wNh1Smvc9WnJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@cacheable/memory": "^2.0.7", + "@cacheable/utils": "^2.3.3", + "hookified": "^1.15.0", + "keyv": "^5.5.5", + "qified": "^0.6.0" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1574,6 +1864,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -1601,7 +1901,6 @@ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "license": "MIT", - "peer": true, "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1662,6 +1961,13 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/colord": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz", + "integrity": "sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==", + "dev": true, + "license": "MIT" + }, "node_modules/colorjs.io": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/colorjs.io/-/colorjs.io-0.5.2.tgz", @@ -1685,6 +1991,7 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 6" } @@ -1795,6 +2102,7 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -1857,6 +2165,70 @@ "url": "https://opencollective.com/express" } }, + "node_modules/cosmiconfig": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", + "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/css-functions-list": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/css-functions-list/-/css-functions-list-3.3.3.tgz", + "integrity": "sha512-8HFEBPKhOpJPEPu70wJJetjKta86Gw9+CCyCnB3sui2qQfOvRyqBy4IKLKKAwdMpWb2lHXWk9Wb4Z6AmaUT1Pg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/css-tree": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.1.0.tgz", + "integrity": "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.12.2", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/dayjs": { "version": "1.11.19", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", @@ -1929,6 +2301,19 @@ "node": ">= 0.8.0" } }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -2083,6 +2468,26 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, "node_modules/es-define-property": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", @@ -2134,7 +2539,6 @@ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==", "hasInstallScript": true, "license": "MIT", - "peer": true, "bin": { "esbuild": "bin/esbuild" }, @@ -2219,6 +2623,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -2317,10 +2722,35 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", + "peer": true, "engines": { "node": ">=6.6.0" } }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, "node_modules/fast-string-truncated-width": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-3.0.3.tgz", @@ -2336,6 +2766,23 @@ "fast-string-truncated-width": "^3.0.2" } }, + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/fast-wrap-ansi": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.2.0.tgz", @@ -2345,6 +2792,36 @@ "fast-string-width": "^3.0.2" } }, + "node_modules/fastest-levenshtein": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", + "integrity": "sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4.9.1" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "11.1.2", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-11.1.2.tgz", + "integrity": "sha512-N2WFfK12gmrK1c1GXOqiAJ1tc5YE+R53zvQ+t5P8S5XhnmKYVB5eZEiLNZKDSmoG8wqqbF9EXYBBW/nef19log==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^6.1.20" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2362,6 +2839,7 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -2378,7 +2856,26 @@ "url": "https://opencollective.com/express" } }, - "node_modules/follow-redirects": { + "node_modules/flat-cache": { + "version": "6.1.20", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-6.1.20.tgz", + "integrity": "sha512-AhHYqwvN62NVLp4lObVXGVluiABTHapoB57EyegZVmazN+hhGhLTn3uZbOofoTw4DSDvVCadzzyChXhOAvy8uQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cacheable": "^2.3.2", + "flatted": "^3.3.3", + "hookified": "^1.15.0" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/follow-redirects": { "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", @@ -2440,6 +2937,7 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -2449,6 +2947,7 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.8" } @@ -2545,6 +3044,72 @@ "node": ">= 6" } }, + "node_modules/global-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", + "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "global-prefix": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/global-prefix": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", + "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ini": "^1.3.5", + "kind-of": "^6.0.2", + "which": "^1.3.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby/node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/globjoin": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/globjoin/-/globjoin-0.1.4.tgz", + "integrity": "sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==", + "dev": true, + "license": "MIT" + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2599,6 +3164,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hashery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/hashery/-/hashery-1.5.0.tgz", + "integrity": "sha512-nhQ6ExaOIqti2FDWoEMWARUqIKyjr2VcZzXShrI+A3zpeiuPWzx6iPftt44LhP74E5sW36B75N6VHbvRtpvO6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.14.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -2611,6 +3189,26 @@ "node": ">= 0.4" } }, + "node_modules/hookified": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/hookified/-/hookified-1.15.1.tgz", + "integrity": "sha512-MvG/clsADq1GPM2KGo2nyfaWVyn9naPiXrqIe4jYjXNZQt238kWyOGrsyc/DmRAQ+Re6yeo6yX/yoNCG5KAEVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-tags": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", + "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/http-errors": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", @@ -2675,6 +3273,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, "node_modules/ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", @@ -2690,21 +3298,73 @@ "node": ">=0.10.0" } }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/import-fresh/node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.10" } }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, "node_modules/is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", @@ -2780,11 +3440,22 @@ "lodash.isfinite": "^3.3.2" } }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-promise": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/is-wsl": { "version": "1.1.0", @@ -2795,6 +3466,13 @@ "node": ">=4" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, "node_modules/joi": { "version": "18.0.2", "resolved": "https://registry.npmjs.org/joi/-/joi-18.0.2.tgz", @@ -2813,6 +3491,40 @@ "node": ">= 20" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/jsonfile": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", @@ -2822,11 +3534,45 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-5.6.0.tgz", + "integrity": "sha512-CYDD3SOtsHtyXeEORYRx2qBtpDJFjRTGXUtmNEMGyzYOKj1TE3tycdlho7kA1Ufx9OYWZzg52QFBGALTirzDSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@keyv/serialize": "^1.1.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/known-css-properties": { + "version": "0.37.0", + "resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.37.0.tgz", + "integrity": "sha512-JCDrsP4Z1Sb9JwG0aJ8Eo2r7k4Ou5MwmThS/6lcIe1ICyb7UBJKGRIUUdqc2ASdE/42lgz6zFUnzAIhtXnBVrQ==", + "dev": true, + "license": "MIT" + }, "node_modules/limiter": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz", "integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA==" }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", @@ -2848,6 +3594,13 @@ "integrity": "sha512-7FGG40uhC8Mm633uKW1r58aElFlBlxCrg9JfSi3P6aYiWmfiWF0PgMd86ZUsxE5GwWPdHoS2+48bwTh2VPkIQA==", "license": "MIT" }, + "node_modules/lodash.truncate": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", + "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", + "dev": true, + "license": "MIT" + }, "node_modules/markdown-it": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", @@ -2874,6 +3627,24 @@ "node": ">= 0.4" } }, + "node_modules/mathml-tag-names": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/mathml-tag-names/-/mathml-tag-names-2.1.3.tgz", + "integrity": "sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/mdn-data": { + "version": "2.12.2", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.12.2.tgz", + "integrity": "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -2889,11 +3660,25 @@ "node": ">= 0.8" } }, + "node_modules/meow": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/meow/-/meow-13.2.0.tgz", + "integrity": "sha512-pxQJQzB6djGPXh08dacEloMFopsOqGVRKFPYvPOt9XDZ1HasbgDZA74CJGreSU4G3Ak7EFJGoiH2auq+yXISgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/merge-descriptors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -2901,6 +3686,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/micromatch": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", @@ -2993,11 +3788,31 @@ "node": "^20.17.0 || >=22.9.0" } }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", + "peer": true, "engines": { "node": ">= 0.6" } @@ -3007,7 +3822,6 @@ "resolved": "https://registry.npmjs.org/nhsuk-frontend/-/nhsuk-frontend-10.3.1.tgz", "integrity": "sha512-R9DH31TfTA3fgi3U0jSVO6wxXEAQY9j5pYzRF4lB3M/Kx3UxvN+oCCWRjTKC/L6wqcddX3qzByl5wKe3PdNX4Q==", "license": "MIT", - "peer": true, "engines": { "node": "^20.9.0 || ^22.11.0 || ^24.11.0" } @@ -3184,6 +3998,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", + "peer": true, "dependencies": { "wrappy": "1" } @@ -3200,6 +4015,38 @@ "node": ">=4" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -3220,11 +4067,29 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", "license": "MIT", + "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, "node_modules/picomatch": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", @@ -3260,11 +4125,156 @@ "npm": ">=1.0.0" } }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-media-query-parser": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/postcss-media-query-parser/-/postcss-media-query-parser-0.2.3.tgz", + "integrity": "sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-resolve-nested-selector": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.6.tgz", + "integrity": "sha512-0sglIs9Wmkzbr8lQwEyIzlDOOC9bGmfVKcJTaxv3vMmd3uo4o4DerC3En0bnmgceeql9BfC8hRkp7cg0fjdVqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss-safe-parser": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-safe-parser/-/postcss-safe-parser-7.0.1.tgz", + "integrity": "sha512-0AioNCJZ2DPYz5ABT6bddIqlhgwhpHZ/l65YAYo0BCIn0xiDpsnTHz0gnoTGk0OXZW0JRs+cDwL8u/teRdz+8A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-safe-parser" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0" + }, + "peerDependencies": { + "postcss": "^8.4.31" + } + }, + "node_modules/postcss-scss": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-scss/-/postcss-scss-4.0.9.tgz", + "integrity": "sha512-AjKOeiwAitL/MXxQW2DliT28EKukvvbEWx3LBmJIRN8KfBGZbRTxNYW0kSqi1COiTZ57nZ9NW06S6ux//N1c9A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss-scss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.4.29" + } + }, + "node_modules/postcss-selector-parser": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-7.1.1.tgz", + "integrity": "sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-sorting": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/postcss-sorting/-/postcss-sorting-9.1.0.tgz", + "integrity": "sha512-Mn8KJ45HNNG6JBpBizXcyf6LqY/qyqetGcou/nprDnFwBFBLGj0j/sNKV2lj2KMOVOwdXu14aEzqJv8CIV6e8g==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "postcss": "^8.4.20" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", + "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -3294,6 +4304,19 @@ "node": ">=6" } }, + "node_modules/qified": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/qified/-/qified-0.6.0.tgz", + "integrity": "sha512-tsSGN1x3h569ZSU1u6diwhltLyfUWDp3YbFHedapTmpBl0B3P6U3+Qptg7xu+v+1io1EwhdPyyRHYbEw0KN2FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hookified": "^1.14.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/qs": { "version": "6.15.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", @@ -3309,6 +4332,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", @@ -3363,6 +4407,16 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", @@ -3389,6 +4443,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/resp-modifier": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/resp-modifier/-/resp-modifier-6.0.2.tgz", @@ -3416,11 +4480,23 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/router": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -3432,6 +4508,30 @@ "node": ">= 18" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/rx": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", @@ -3498,7 +4598,6 @@ "resolved": "https://registry.npmjs.org/sass-embedded/-/sass-embedded-1.97.3.tgz", "integrity": "sha512-eKzFy13Nk+IRHhlAwP3sfuv+PzOrvzUkwJK2hdoCKYcWGSdmwFpeGpWmyewdw8EgBnsKaSBtgf/0b2K635ecSA==", "license": "MIT", - "peer": true, "dependencies": { "@bufbuild/protobuf": "^2.5.0", "colorjs.io": "^0.5.0", @@ -3895,6 +4994,7 @@ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", + "peer": true, "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", @@ -4035,6 +5135,7 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", + "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -4157,6 +5258,34 @@ "node": ">=10" } }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, "node_modules/socket.io": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", @@ -4322,6 +5451,234 @@ "node": ">=8" } }, + "node_modules/stylelint": { + "version": "16.26.1", + "resolved": "https://registry.npmjs.org/stylelint/-/stylelint-16.26.1.tgz", + "integrity": "sha512-v20V59/crfc8sVTAtge0mdafI3AdnzQ2KsWe6v523L4OA1bJO02S7MO2oyXDCS6iWb9ckIPnqAFVItqSBQr7jw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/css-parser-algorithms": "^3.0.5", + "@csstools/css-syntax-patches-for-csstree": "^1.0.19", + "@csstools/css-tokenizer": "^3.0.4", + "@csstools/media-query-list-parser": "^4.0.3", + "@csstools/selector-specificity": "^5.0.0", + "@dual-bundle/import-meta-resolve": "^4.2.1", + "balanced-match": "^2.0.0", + "colord": "^2.9.3", + "cosmiconfig": "^9.0.0", + "css-functions-list": "^3.2.3", + "css-tree": "^3.1.0", + "debug": "^4.4.3", + "fast-glob": "^3.3.3", + "fastest-levenshtein": "^1.0.16", + "file-entry-cache": "^11.1.1", + "global-modules": "^2.0.0", + "globby": "^11.1.0", + "globjoin": "^0.1.4", + "html-tags": "^3.3.1", + "ignore": "^7.0.5", + "imurmurhash": "^0.1.4", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mathml-tag-names": "^2.1.3", + "meow": "^13.2.0", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.5.6", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-safe-parser": "^7.0.1", + "postcss-selector-parser": "^7.1.0", + "postcss-value-parser": "^4.2.0", + "resolve-from": "^5.0.0", + "string-width": "^4.2.3", + "supports-hyperlinks": "^3.2.0", + "svg-tags": "^1.0.0", + "table": "^6.9.0", + "write-file-atomic": "^5.0.1" + }, + "bin": { + "stylelint": "bin/stylelint.mjs" + }, + "engines": { + "node": ">=18.12.0" + } + }, + "node_modules/stylelint-config-gds": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/stylelint-config-gds/-/stylelint-config-gds-2.0.0.tgz", + "integrity": "sha512-uloFaElSPR25oJ+tTCO0oLmuiq6qpFMPUwKRz90dGA9eEE+37ljd718P3GFwk5dNNtC3hr3KtNqW8kQOJvgugg==", + "dev": true, + "license": "MIT", + "dependencies": { + "stylelint-config-standard": "^36.0.0", + "stylelint-config-standard-scss": "^13.0.0" + }, + "peerDependencies": { + "stylelint": "^16.0.2" + } + }, + "node_modules/stylelint-config-recommended": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended/-/stylelint-config-recommended-14.0.1.tgz", + "integrity": "sha512-bLvc1WOz/14aPImu/cufKAZYfXs/A/owZfSMZ4N+16WGXLoX5lOir53M6odBxvhgmgdxCVnNySJmZKx73T93cg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint-config-recommended-scss": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-recommended-scss/-/stylelint-config-recommended-scss-14.1.0.tgz", + "integrity": "sha512-bhaMhh1u5dQqSsf6ri2GVWWQW5iUjBYgcHkh7SgDDn92ijoItC/cfO/W+fpXshgTQWhwFkP1rVcewcv4jaftRg==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-scss": "^4.0.9", + "stylelint-config-recommended": "^14.0.1", + "stylelint-scss": "^6.4.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^16.6.1" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-config-standard": { + "version": "36.0.1", + "resolved": "https://registry.npmjs.org/stylelint-config-standard/-/stylelint-config-standard-36.0.1.tgz", + "integrity": "sha512-8aX8mTzJ6cuO8mmD5yon61CWuIM4UD8Q5aBcWKGSf6kg+EC3uhB+iOywpTK4ca6ZL7B49en8yanOFtUW0qNzyw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/stylelint" + }, + { + "type": "github", + "url": "https://github.com/sponsors/stylelint" + } + ], + "license": "MIT", + "dependencies": { + "stylelint-config-recommended": "^14.0.1" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.1.0" + } + }, + "node_modules/stylelint-config-standard-scss": { + "version": "13.1.0", + "resolved": "https://registry.npmjs.org/stylelint-config-standard-scss/-/stylelint-config-standard-scss-13.1.0.tgz", + "integrity": "sha512-Eo5w7/XvwGHWkeGLtdm2FZLOMYoZl1omP2/jgFCXyl2x5yNz7/8vv4Tj6slHvMSSUNTaGoam/GAZ0ZhukvalfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "stylelint-config-recommended-scss": "^14.0.0", + "stylelint-config-standard": "^36.0.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "postcss": "^8.3.3", + "stylelint": "^16.3.1" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + } + } + }, + "node_modules/stylelint-order": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/stylelint-order/-/stylelint-order-7.0.1.tgz", + "integrity": "sha512-GWPei1zBVDDjxM+/BmcSCiOcHNd8rSqW6FUZtqQGlTRpD0Z5nSzspzWD8rtKif5KPdzUG68DApKEV/y/I9VbTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss": "^8.5.6", + "postcss-sorting": "^9.1.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "stylelint": "^16.18.0 || ^17.0.0" + } + }, + "node_modules/stylelint-scss": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/stylelint-scss/-/stylelint-scss-6.14.0.tgz", + "integrity": "sha512-ZKmHMZolxeuYsnB+PCYrTpFce0/QWX9i9gh0hPXzp73WjuIMqUpzdQaBCrKoLWh6XtCFSaNDErkMPqdjy1/8aA==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.1", + "is-plain-object": "^5.0.0", + "known-css-properties": "^0.37.0", + "mdn-data": "^2.25.0", + "postcss-media-query-parser": "^0.2.3", + "postcss-resolve-nested-selector": "^0.1.6", + "postcss-selector-parser": "^7.1.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": ">=18.12.0" + }, + "peerDependencies": { + "stylelint": "^16.8.2" + } + }, + "node_modules/stylelint-scss/node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/stylelint/node_modules/balanced-match": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-2.0.0.tgz", + "integrity": "sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==", + "dev": true, + "license": "MIT" + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -4334,6 +5691,23 @@ "node": ">=8" } }, + "node_modules/supports-hyperlinks": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-3.2.0.tgz", + "integrity": "sha512-zFObLMyZeEwzAoKCyu1B91U79K2t7ApXuQfo8OuxwXLDgcKxuwM+YvcbIhm6QWqz7mHUH1TVytR1PwVVjEuMig==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0", + "supports-color": "^7.0.0" + }, + "engines": { + "node": ">=14.18" + }, + "funding": { + "url": "https://github.com/chalk/supports-hyperlinks?sponsor=1" + } + }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -4346,6 +5720,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-tags": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", + "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", + "dev": true + }, "node_modules/sync-child-process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/sync-child-process/-/sync-child-process-1.0.2.tgz", @@ -4367,6 +5747,23 @@ "node": ">=16.0.0" } }, + "node_modules/table": { + "version": "6.9.0", + "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", + "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "ajv": "^8.0.1", + "lodash.truncate": "^4.4.2", + "slice-ansi": "^4.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -4491,6 +5888,13 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -4540,6 +5944,19 @@ "integrity": "sha512-d0actwV3zgtH73koFWida36KVz+hM1XaumMBTQI6ozr9zUgHiB4dWM+2wWcXe6cniyLW0iMBthuPN6XtSpPMVw==", "license": "MIT" }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", @@ -4561,7 +5978,22 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" + "license": "ISC", + "peer": true + }, + "node_modules/write-file-atomic": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "imurmurhash": "^0.1.4", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } }, "node_modules/ws": { "version": "8.18.3", diff --git a/package.json b/package.json index 3c0b51a3..1bbb40f9 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,13 @@ "description": "Rapidly create HTML prototypes of NHS websites and services.", "main": "app.js", "scripts": { - "start": "node ." + "start": "node .", + "lint": "npm run lint:css && npm run lint:prettier", + "lint:fix": "npm run lint:css:fix && npm run lint:prettier:fix", + "lint:prettier": "prettier --cache --cache-location .cache/prettier --cache-strategy content --check .", + "lint:prettier:fix": "prettier --write .", + "lint:css": "stylelint --cache --cache-location .cache/stylelint --cache-strategy content --color --formatter verbose --ignore-path .gitignore --max-warnings 0 \"**/*.scss\"", + "lint:css:fix": "npm run lint:css -- --fix" }, "author": "https://github.com/nhsuk/", "license": "MIT", @@ -19,6 +25,14 @@ "pluralize": "^8.0.0", "weighted": "^1.0.0" }, + "devDependencies": { + "postcss": "^8.5.6", + "postcss-scss": "^4.0.9", + "prettier": "^3.8.1", + "stylelint": "^16.26.1", + "stylelint-config-gds": "^2.0.0", + "stylelint-order": "^7.0.1" + }, "engines": { "node": "^22.16.0 || ^24.11.0", "npm": "^11.6.1" diff --git a/stylelint.config.mjs b/stylelint.config.mjs new file mode 100644 index 00000000..1f0013f6 --- /dev/null +++ b/stylelint.config.mjs @@ -0,0 +1,283 @@ +/** + * @type {Config} + */ +export default { + extends: 'stylelint-config-gds/scss', + ignoreFiles: [ + '**/public/**', + + // Ignore CSS-in-JS (including dotfiles) + '**/?(.)*.{cjs,js,mjs}', + + // Prevent CHANGELOG history changes + 'CHANGELOG.md' + ], + overrides: [ + { + customSyntax: 'postcss-scss', + files: ['**/*.scss'] + } + ], + plugins: ['stylelint-order'], + rules: { + /** + * Prefer GOV.UK Frontend property order + * + * @see {@link https://github.com/alphagov/govuk-frontend/blob/main/stylelint.config.js} + * @see {@link https://github.com/hudochenkov/stylelint-order/blob/master/rules/properties-order/README.md} + */ + 'order/properties-order': [ + [ + { + emptyLineBefore: 'threshold', + properties: ['content', 'content-visibility', 'quotes'] + }, + { + emptyLineBefore: 'threshold', + properties: ['box-sizing', 'display', 'visibility'] + }, + { + emptyLineBefore: 'threshold', + properties: ['position', 'z-index', 'top', 'right', 'bottom', 'left'] + }, + { + emptyLineBefore: 'threshold', + properties: [ + 'flex', + 'flex-basis', + 'flex-direction', + 'flex-flow', + 'flex-grow', + 'flex-shrink', + 'flex-wrap', + 'align-content', + 'align-items', + 'align-self', + 'justify-content', + 'order', + + 'grid', + 'grid-area', + 'grid-auto-columns', + 'grid-auto-flow', + 'grid-auto-rows', + 'grid-column', + 'grid-column-end', + 'grid-column-start', + 'grid-row', + 'grid-row-end', + 'grid-row-start', + 'grid-template', + 'grid-template-areas', + 'grid-template-columns', + 'grid-template-rows', + + 'columns', + 'column-count', + 'column-fill', + 'column-gap', + 'column-rule', + 'column-rule-color', + 'column-rule-style', + 'column-rule-width', + 'column-span', + 'column-width', + 'row-gap' + ] + }, + { + emptyLineBefore: 'threshold', + properties: [ + 'width', + 'min-width', + 'max-width', + 'height', + 'min-height', + 'max-height', + + 'margin', + 'margin-top', + 'margin-right', + 'margin-bottom', + 'margin-left', + + 'padding', + 'padding-top', + 'padding-right', + 'padding-bottom', + 'padding-left' + ] + }, + { + emptyLineBefore: 'threshold', + properties: ['float', 'clear', 'overflow', 'overflow-x', 'overflow-y'] + }, + { + emptyLineBefore: 'threshold', + properties: ['clip', 'clip-path', 'zoom', 'resize'] + }, + { + emptyLineBefore: 'threshold', + properties: [ + 'table-layout', + 'empty-cells', + 'caption-side', + 'border-spacing', + 'border-collapse' + ] + }, + { + emptyLineBefore: 'threshold', + properties: [ + 'list-style', + 'list-style-position', + 'list-style-type', + 'list-style-image' + ] + }, + { + emptyLineBefore: 'threshold', + properties: ['transform', 'transition', 'animation'] + }, + { + emptyLineBefore: 'threshold', + properties: [ + 'border', + 'border-top', + 'border-right', + 'border-bottom', + 'border-left', + + 'border-width', + 'border-top-width', + 'border-right-width', + 'border-bottom-width', + 'border-left-width', + + 'border-style', + 'border-top-style', + 'border-right-style', + 'border-bottom-style', + 'border-left-style', + + 'border-radius', + 'border-top-left-radius', + 'border-top-right-radius', + 'border-bottom-left-radius', + 'border-bottom-right-radius', + + 'border-color', + 'border-top-color', + 'border-right-color', + 'border-bottom-color', + 'border-left-color', + + 'border-image', + + 'outline', + 'outline-color', + 'outline-offset', + 'outline-style', + 'outline-width' + ] + }, + { + emptyLineBefore: 'threshold', + properties: ['pointer-events', 'opacity'] + }, + { + // Color has been moved to ensure it appears before background + emptyLineBefore: 'threshold', + properties: [ + 'color', + 'background', + 'background-color', + 'background-image', + 'background-repeat', + 'background-position', + 'background-size', + 'box-shadow', + 'fill', + + 'mask', + 'mask-border', + 'mask-border-mode', + 'mask-border-outset', + 'mask-border-repeat', + 'mask-border-slice', + 'mask-border-source', + 'mask-border-width', + 'mask-clip', + 'mask-composite', + 'mask-image', + 'mask-mode', + 'mask-origin', + 'mask-repeat', + 'mask-position', + 'mask-size', + 'mask-type' + ] + }, + { + emptyLineBefore: 'threshold', + properties: [ + 'font', + 'font-family', + 'font-size', + 'font-style', + 'font-variant', + 'font-weight', + 'font-emphasize', + 'font-display', + 'src', + + 'letter-spacing', + 'line-height', + 'word-spacing', + + 'text-align', + 'text-align-last', + 'text-decoration', + 'text-decoration-thickness', + 'text-decoration-skip-ink', + 'text-decoration-skip', + 'text-indent', + 'text-justify', + 'text-overflow', + 'text-overflow-ellipsis', + 'text-overflow-mode', + 'text-rendering', + 'text-outline', + 'text-shadow', + 'text-transform', + 'text-wrap', + 'word-wrap', + 'word-break', + 'overflow-wrap', + + 'text-emphasis', + + 'vertical-align', + 'white-space', + 'word-spacing', + 'hyphens', + 'user-select', + 'forced-color-adjust' + ] + }, + { + emptyLineBefore: 'threshold', + properties: ['cursor', '-webkit-appearance'] + } + ], + { + emptyLineBeforeUnspecified: 'threshold', + emptyLineMinimumPropertyThreshold: 6 + } + ] + } +} + +/** + * @import { Config } from 'stylelint' + */ From 4b5809da4fc9abc5038ea0d5379dc581b9ed19b9 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Mon, 9 Mar 2026 14:44:29 +0000 Subject: [PATCH 3/5] Fix Prettier formatting --- app/assets/javascript/keyboard-shortcuts.js | 58 ++++++++++--------- app/assets/javascript/main.js | 10 +++- app/filters/formatting.js | 36 ++++++------ app/filters/nunjucks.js | 19 ++---- .../generators/appointment-note-generator.js | 13 ++--- .../medical-information/hrt-generator.js | 13 +++-- .../pregnancy-and-breastfeeding-generator.js | 39 +++++++------ app/lib/utils/dates.js | 16 ++--- app/lib/utils/dynamic-routing.js | 6 +- app/lib/utils/reading.js | 6 +- app/lib/utils/referrers.js | 4 +- app/locals.js | 5 +- app/routes.js | 27 +++++---- notes/DATA-GENERATOR-REFERENCE.md | 8 +++ notes/MEDICAL-INFORMATION-GENERATOR-GUIDE.md | 7 +++ notes/previous-mammograms-edit-delete.md | 8 +++ 16 files changed, 160 insertions(+), 115 deletions(-) diff --git a/app/assets/javascript/keyboard-shortcuts.js b/app/assets/javascript/keyboard-shortcuts.js index 8d24b40d..6c4b9ce4 100644 --- a/app/assets/javascript/keyboard-shortcuts.js +++ b/app/assets/javascript/keyboard-shortcuts.js @@ -1,7 +1,7 @@ // app/assets/javascript/keyboard-shortcuts.js // Keyboard shortcut handling for reading workflow -const CHANNEL_NAME = "mammogram-viewer" +const CHANNEL_NAME = 'mammogram-viewer' /** * Play an alert sound when shortcut is blocked (e.g., during lockout) @@ -9,17 +9,19 @@ const CHANNEL_NAME = "mammogram-viewer" const playAlertSound = () => { // Create a short beep using Web Audio API try { - const audioContext = new (window.AudioContext || window.webkitAudioContext)() + const audioContext = new ( + window.AudioContext || window.webkitAudioContext + )() const oscillator = audioContext.createOscillator() const gainNode = audioContext.createGain() - + oscillator.connect(gainNode) gainNode.connect(audioContext.destination) - + oscillator.frequency.value = 440 // A4 note - oscillator.type = "sine" + oscillator.type = 'sine' gainNode.gain.value = 0.3 - + oscillator.start() oscillator.stop(audioContext.currentTime + 0.15) // Short beep } catch (e) { @@ -34,9 +36,9 @@ const isInFormField = (element) => { if (!element) return false const tagName = element.tagName.toLowerCase() return ( - tagName === "input" || - tagName === "textarea" || - tagName === "select" || + tagName === 'input' || + tagName === 'textarea' || + tagName === 'select' || element.isContentEditable ) } @@ -45,8 +47,8 @@ const isInFormField = (element) => { * Check if the opinion form is locked (during initial delay) */ const isOpinionLocked = () => { - const form = document.querySelector("[data-reading-opinion-locked]") - return form && form.getAttribute("data-reading-opinion-locked") === "true" + const form = document.querySelector('[data-reading-opinion-locked]') + return form && form.getAttribute('data-reading-opinion-locked') === 'true' } /** @@ -58,7 +60,7 @@ const isOpinionLocked = () => { const triggerShortcut = (key) => { // Check if opinion is locked - play alert sound if so if (isOpinionLocked()) { - console.log("[Shortcut] Blocked - opinion is locked") + console.log('[Shortcut] Blocked - opinion is locked') playAlertSound() return false } @@ -66,7 +68,7 @@ const triggerShortcut = (key) => { // First, try button/submit elements with data-shortcut const button = document.querySelector(`[data-shortcut="${key}"]`) if (button && !button.disabled) { - console.log("[Shortcut] Triggering:", key) + console.log('[Shortcut] Triggering:', key) button.click() return true } @@ -74,12 +76,12 @@ const triggerShortcut = (key) => { // Then, try radio inputs with data-shortcut-radio (attribute is on the input) const radio = document.querySelector(`input[data-shortcut-radio="${key}"]`) if (radio && !radio.disabled) { - console.log("[Shortcut] Triggering:", key) + console.log('[Shortcut] Triggering:', key) radio.checked = true - radio.dispatchEvent(new Event("change", { bubbles: true })) - + radio.dispatchEvent(new Event('change', { bubbles: true })) + // Submit the form - const form = radio.closest("form") + const form = radio.closest('form') if (form) { form.submit() } @@ -95,12 +97,12 @@ const triggerShortcut = (key) => { const initReadingShortcuts = () => { // Get the broadcast channel for cross-window communication let channel = null - if (typeof BroadcastChannel !== "undefined") { + if (typeof BroadcastChannel !== 'undefined') { channel = new BroadcastChannel(CHANNEL_NAME) } // Handle local keyboard events - document.addEventListener("keydown", (e) => { + document.addEventListener('keydown', (e) => { // Ignore if in a form field if (isInFormField(e.target)) return @@ -113,8 +115,8 @@ const initReadingShortcuts = () => { // Listen for shortcut messages from PACS viewer if (channel) { - channel.addEventListener("message", (event) => { - if (event.data.type === "shortcut") { + channel.addEventListener('message', (event) => { + if (event.data.type === 'shortcut') { triggerShortcut(event.data.key) } }) @@ -127,16 +129,16 @@ const initReadingShortcuts = () => { */ const initViewerShortcutForwarding = () => { let channel = null - if (typeof BroadcastChannel !== "undefined") { + if (typeof BroadcastChannel !== 'undefined') { channel = new BroadcastChannel(CHANNEL_NAME) } if (!channel) return // Define which keys to forward to the reading page - const forwardKeys = ["n", "t", "r"] + const forwardKeys = ['n', 't', 'r'] - document.addEventListener("keydown", (e) => { + document.addEventListener('keydown', (e) => { // Ignore if modifier keys are pressed if (e.ctrlKey || e.altKey || e.metaKey) return @@ -145,7 +147,7 @@ const initViewerShortcutForwarding = () => { // Forward reading shortcuts to the reading page if (forwardKeys.includes(key)) { channel.postMessage({ - type: "shortcut", + type: 'shortcut', key: key, timestamp: Date.now() }) @@ -154,9 +156,11 @@ const initViewerShortcutForwarding = () => { } // Auto-initialise on reading pages (pages with shortcut elements) -document.addEventListener("DOMContentLoaded", () => { +document.addEventListener('DOMContentLoaded', () => { // Check if this page has any shortcut elements (buttons or radios) - const hasShortcuts = document.querySelector("[data-shortcut], [data-shortcut-radio]") + const hasShortcuts = document.querySelector( + '[data-shortcut], [data-shortcut-radio]' + ) if (hasShortcuts) { initReadingShortcuts() } diff --git a/app/assets/javascript/main.js b/app/assets/javascript/main.js index 3780610f..bfcafbae 100644 --- a/app/assets/javascript/main.js +++ b/app/assets/javascript/main.js @@ -102,9 +102,13 @@ document.addEventListener('DOMContentLoaded', () => { setTimeout(() => { opinionBanner.classList.add('app-reading-opinion-banner--fade-out') // Remove from DOM after the CSS transition (0.2s) completes - opinionBanner.addEventListener('transitionend', () => { - opinionBanner.remove() - }, { once: true }) + opinionBanner.addEventListener( + 'transitionend', + () => { + opinionBanner.remove() + }, + { once: true } + ) }, delay) } diff --git a/app/filters/formatting.js b/app/filters/formatting.js index b61fbb05..e5745217 100644 --- a/app/filters/formatting.js +++ b/app/filters/formatting.js @@ -39,28 +39,30 @@ const formatAnswer = (value, options = {}) => { } // Convert an integer to its ordinal name (first, second, third, etc) -const getOrdinalName = (integer) => -{ +const getOrdinalName = (integer) => { const ordinals = [ - "zeroth", // shouldn't be possible - "first", - "second", - "third", - "fourth", - "fifth", - "sixth", - "seventh", - "eighth", - "ninth", - "tenth" + 'zeroth', // shouldn't be possible + 'first', + 'second', + 'third', + 'fourth', + 'fifth', + 'sixth', + 'seventh', + 'eighth', + 'ninth', + 'tenth' ] const parsedInteger = Number(integer) - if (!Number.isInteger(parsedInteger) || parsedInteger < 1 || parsedInteger > 10) - { - console.warn("Error in getOrdinalName: input out of bounds") - return "" + if ( + !Number.isInteger(parsedInteger) || + parsedInteger < 1 || + parsedInteger > 10 + ) { + console.warn('Error in getOrdinalName: input out of bounds') + return '' } return ordinals[parsedInteger] diff --git a/app/filters/nunjucks.js b/app/filters/nunjucks.js index 2d8a34a9..a282e0e6 100644 --- a/app/filters/nunjucks.js +++ b/app/filters/nunjucks.js @@ -139,33 +139,26 @@ const getContext = function () { * @param {*} value - The value to parse (string) or return (object/array) * @returns {*} The parsed object, original structured data, or null if parsing failed */ -function parseJsonString(value) -{ +function parseJsonString(value) { // Handle null, undefined, or empty string - if (value === null || value === undefined || value === '') - { + if (value === null || value === undefined || value === '') { return null } // If it's already structured data (object or array), return it as-is - if (typeof value === 'object') - { + if (typeof value === 'object') { return value } // If it's not a string, convert to string first (for numbers, booleans, etc.) - if (typeof value !== 'string') - { + if (typeof value !== 'string') { value = String(value) } // Only attempt JSON.parse on strings - try - { + try { return JSON.parse(value) - } - catch (error) - { + } catch (error) { console.warn('Failed to parse JSON string:', value, error) return null } diff --git a/app/lib/generators/appointment-note-generator.js b/app/lib/generators/appointment-note-generator.js index ee70f3b9..73c49d80 100644 --- a/app/lib/generators/appointment-note-generator.js +++ b/app/lib/generators/appointment-note-generator.js @@ -6,11 +6,11 @@ const weighted = require('weighted') // Categories of appointment notes with realistic examples const NOTE_CATEGORIES = { medical: { - weight: 0.10, + weight: 0.1, notes: [ `Pain in left breast, GP thinks it's blocked ducts and has given antibiotics`, 'Aching on left side', - 'Shoulder pain', + 'Shoulder pain' ] }, @@ -20,7 +20,7 @@ const NOTE_CATEGORIES = { 'Family history of breast cancer (both sisters)', 'Maternal cousin breast cancer at 46 years', 'Sister breast cancer at 50 years', - 'Family history of cancer - grandma at 40 years old', + 'Family history of cancer - grandma at 40 years old' ] }, @@ -30,11 +30,10 @@ const NOTE_CATEGORIES = { 'Very nervous', 'Unable to tolerate full compression', 'Difficulty with compression, consent given to complete', - 'Difficult, kept moving, pulled out of machine, compression sensitive', + 'Difficult, kept moving, pulled out of machine, compression sensitive' ] }, - communication: { weight: 0.2, notes: [ @@ -60,7 +59,7 @@ const NOTE_CATEGORIES = { /** * Generate an appointment note for an event - * + * * @param {object} options - Generation options * @param {boolean} options.isScheduled - Whether the event is scheduled (not yet happened) * @param {boolean} options.isCompleted - Whether the event is completed @@ -102,4 +101,4 @@ module.exports = { generateAppointmentNote, // Export for testing NOTE_CATEGORIES -} \ No newline at end of file +} diff --git a/app/lib/generators/medical-information/hrt-generator.js b/app/lib/generators/medical-information/hrt-generator.js index e4a98874..8de16a96 100644 --- a/app/lib/generators/medical-information/hrt-generator.js +++ b/app/lib/generators/medical-information/hrt-generator.js @@ -50,9 +50,9 @@ const generateHRT = (options = {}) => { // Weighted selection of HRT status const hrtQuestion = weighted.select({ - 'yes': 0.5, // Currently taking - 'no-recently-stopped': 0.3, // Recently stopped - 'no': 0.2 // No HRT + 'yes': 0.5, // Currently taking + 'no-recently-stopped': 0.3, // Recently stopped + 'no': 0.2 // No HRT }) const hrt = { hrtQuestion } @@ -60,10 +60,11 @@ const generateHRT = (options = {}) => { // Add conditional fields based on status if (hrtQuestion === 'yes') { hrt.hrtDuration = faker.helpers.arrayElement(CURRENT_HRT_DURATIONS) - } - else if (hrtQuestion === 'no-recently-stopped') { + } else if (hrtQuestion === 'no-recently-stopped') { hrt.hrtDurationSinceStopped = faker.helpers.arrayElement(STOPPED_TIMEFRAMES) - hrt.hrtDurationBeforeStopping = faker.helpers.arrayElement(DURATION_BEFORE_STOPPING) + hrt.hrtDurationBeforeStopping = faker.helpers.arrayElement( + DURATION_BEFORE_STOPPING + ) } return hrt diff --git a/app/lib/generators/medical-information/pregnancy-and-breastfeeding-generator.js b/app/lib/generators/medical-information/pregnancy-and-breastfeeding-generator.js index b3fe3334..6cd5bed8 100644 --- a/app/lib/generators/medical-information/pregnancy-and-breastfeeding-generator.js +++ b/app/lib/generators/medical-information/pregnancy-and-breastfeeding-generator.js @@ -63,44 +63,49 @@ const generatePregnancyAndBreastfeeding = (options = {}) => { // Weighted selection of pregnancy status info.pregnancyStatus = weighted.select({ - 'noNotPregnant': 0.7, // Most common - not pregnant - 'noButRecently': 0.2, // Recently gave birth - 'yes': 0.1 // Currently pregnant + noNotPregnant: 0.7, // Most common - not pregnant + noButRecently: 0.2, // Recently gave birth + yes: 0.1 // Currently pregnant }) // Add conditional fields based on pregnancy status if (info.pregnancyStatus === 'yes') { - info.currentlyPregnantDetails = faker.helpers.arrayElement(PREGNANCY_DETAILS) - } - else if (info.pregnancyStatus === 'noButRecently') { - info.recentlyPregnantDetails = faker.helpers.arrayElement(RECENT_PREGNANCY_DETAILS) + info.currentlyPregnantDetails = + faker.helpers.arrayElement(PREGNANCY_DETAILS) + } else if (info.pregnancyStatus === 'noButRecently') { + info.recentlyPregnantDetails = faker.helpers.arrayElement( + RECENT_PREGNANCY_DETAILS + ) } // Weighted selection of breastfeeding status // If recently pregnant, more likely to be breastfeeding if (info.pregnancyStatus === 'noButRecently') { info.breastfeedingStatus = weighted.select({ - 'yes': 0.6, // Likely breastfeeding if recently gave birth - 'recentlyStopped': 0.2, - 'no': 0.2 + yes: 0.6, // Likely breastfeeding if recently gave birth + recentlyStopped: 0.2, + no: 0.2 }) } else if (info.pregnancyStatus === 'yes') { // If pregnant, not breastfeeding info.breastfeedingStatus = 'no' } else { info.breastfeedingStatus = weighted.select({ - 'no': 0.8, // Most common - 'yes': 0.1, // Currently breastfeeding - 'recentlyStopped': 0.1 // Recently stopped + no: 0.8, // Most common + yes: 0.1, // Currently breastfeeding + recentlyStopped: 0.1 // Recently stopped }) } // Add conditional fields based on breastfeeding status if (info.breastfeedingStatus === 'yes') { - info.currentlyBreastfeedingDuration = faker.helpers.arrayElement(BREASTFEEDING_DURATIONS) - } - else if (info.breastfeedingStatus === 'recentlyStopped') { - info.recentlyBreastfeedingDuration = faker.helpers.arrayElement(RECENTLY_STOPPED_BREASTFEEDING) + info.currentlyBreastfeedingDuration = faker.helpers.arrayElement( + BREASTFEEDING_DURATIONS + ) + } else if (info.breastfeedingStatus === 'recentlyStopped') { + info.recentlyBreastfeedingDuration = faker.helpers.arrayElement( + RECENTLY_STOPPED_BREASTFEEDING + ) } return info diff --git a/app/lib/utils/dates.js b/app/lib/utils/dates.js index 3ffd8323..a8a045f5 100644 --- a/app/lib/utils/dates.js +++ b/app/lib/utils/dates.js @@ -692,13 +692,15 @@ const calculateDurationMinutes = (startTime, endTime) => { if (!startTime || !endTime) return 0 // If it looks like just a time (contains no date), prefix with dummy date - const startDatetime = startTime.includes('T') || startTime.includes('-') - ? startTime - : `2000-01-01T${startTime}` - - const endDatetime = endTime.includes('T') || endTime.includes('-') - ? endTime - : `2000-01-01T${endTime}` + const startDatetime = + startTime.includes('T') || startTime.includes('-') + ? startTime + : `2000-01-01T${startTime}` + + const endDatetime = + endTime.includes('T') || endTime.includes('-') + ? endTime + : `2000-01-01T${endTime}` const start = dayjs(startDatetime) const end = dayjs(endDatetime) diff --git a/app/lib/utils/dynamic-routing.js b/app/lib/utils/dynamic-routing.js index d8ea262d..0eb43532 100644 --- a/app/lib/utils/dynamic-routing.js +++ b/app/lib/utils/dynamic-routing.js @@ -15,9 +15,9 @@ const createDynamicTemplateRoute = (options) => { const { subPaths } = req.params // Get the wildcard paths const subPath = subPaths?.join('/') ?? '' -// console.log( -// `Dynamic route attempting to render: ${templatePrefix}/${subPath}` -// ) + // console.log( + // `Dynamic route attempting to render: ${templatePrefix}/${subPath}` + // ) // Try to render {templatePrefix}/{subPath} const templatePath = `${templatePrefix}/${subPath}` diff --git a/app/lib/utils/reading.js b/app/lib/utils/reading.js index b20645c0..eb1aa6ed 100644 --- a/app/lib/utils/reading.js +++ b/app/lib/utils/reading.js @@ -964,7 +964,11 @@ const getFirstUserReadableEvent = function (events, userId = null) { // Get the next event the user can read after a given event, wrapping to the // start of the list if nothing follows. Returns null if nothing is readable. -const getNextUserReadableEvent = function (events, currentEventId, userId = null) { +const getNextUserReadableEvent = function ( + events, + currentEventId, + userId = null +) { const currentUserId = userId || this?.ctx?.data?.currentUser?.id const currentIndex = events.findIndex((e) => e.id === currentEventId) const eventsFromNext = [ diff --git a/app/lib/utils/referrers.js b/app/lib/utils/referrers.js index b3f3aff0..b2bcfd20 100644 --- a/app/lib/utils/referrers.js +++ b/app/lib/utils/referrers.js @@ -254,12 +254,12 @@ const appendReferrer = (existingReferrerChain, newUrl) => { if (!existingReferrerChain) return newUrl const chain = parseReferrerChain(existingReferrerChain) - + // Don't append if it's already the last item in the chain (prevents duplicates) if (chain.length > 0 && chain[chain.length - 1] === newUrl) { return existingReferrerChain } - + chain.push(newUrl) return chain.join(',') } diff --git a/app/locals.js b/app/locals.js index b944c014..350d70ce 100644 --- a/app/locals.js +++ b/app/locals.js @@ -1,9 +1,10 @@ // app/locals.js module.exports = (req, res, next) => { - const currentUser = req.session.data.currentUser const currentBSU = currentUser - ? req.session.data.breastScreeningUnits?.find(unit => unit.id === currentUser.breastScreeningUnit) + ? req.session.data.breastScreeningUnits?.find( + (unit) => unit.id === currentUser.breastScreeningUnit + ) : null const locals = { diff --git a/app/routes.js b/app/routes.js index 8c97dadc..d6bc4afb 100644 --- a/app/routes.js +++ b/app/routes.js @@ -130,17 +130,24 @@ router.use((req, res, next) => { const fs = require('fs') const path = require('path') -router.post('/admin/mammogram-sets/save', express.json({ limit: '5mb' }), (req, res) => { - try { - const manifestPath = path.join(__dirname, 'assets/images/mammogram-diagrams/manifest.json') - const output = JSON.stringify(req.body, null, 2) - fs.writeFileSync(manifestPath, output, 'utf8') - res.json({ success: true }) - } catch (err) { - console.error('Failed to save manifest:', err) - res.status(500).json({ error: err.message }) +router.post( + '/admin/mammogram-sets/save', + express.json({ limit: '5mb' }), + (req, res) => { + try { + const manifestPath = path.join( + __dirname, + 'assets/images/mammogram-diagrams/manifest.json' + ) + const output = JSON.stringify(req.body, null, 2) + fs.writeFileSync(manifestPath, output, 'utf8') + res.json({ success: true }) + } catch (err) { + console.error('Failed to save manifest:', err) + res.status(500).json({ error: err.message }) + } } -}) +) require('./routes/settings')(router) require('./routes/clinics')(router) diff --git a/notes/DATA-GENERATOR-REFERENCE.md b/notes/DATA-GENERATOR-REFERENCE.md index 7799e1a1..b2974399 100644 --- a/notes/DATA-GENERATOR-REFERENCE.md +++ b/notes/DATA-GENERATOR-REFERENCE.md @@ -39,6 +39,7 @@ generate-seed-data.js (main orchestrator) ``` **Key points:** + - Generators are called sequentially by the main orchestrator - Medical information is only generated for **completed events** - Participants are mostly created upfront, but can be generated on-demand @@ -47,11 +48,13 @@ generate-seed-data.js (main orchestrator) ### Storage Locations **Participant level:** + - Basic demographics - NHS number, GP details - Persistent information **Event level:** + - Medical information collected during appointments - Session details (who, when, where) - Event-specific data @@ -349,6 +352,7 @@ const generateMedicalInformation = (options = {}) => { ``` **Benefits:** + - Single point of orchestration - Default probabilities in one place - Simplified integration with parent generators @@ -449,11 +453,13 @@ const relativeDate = faker.helpers.arrayElement([ ### Testing-Friendly vs Realistic **For user research testing:** + - Higher probabilities to ensure features appear - 30-40% chance of medical history (vs realistic 15-25%) - More edge cases than would occur naturally **For stakeholder demos:** + - Lower, more realistic probabilities - Reflects actual NHS screening population @@ -490,6 +496,7 @@ app/lib/generators/ ``` **Naming conventions:** + - `generateItem()` - Single item - `generateItems()` - Array of items - `generate[Type]()` - Specific type/variant @@ -633,6 +640,7 @@ if (availableParticipants.length === 0) { ## Summary The generator system provides: + - **Hierarchical structure** - Generators call sub-generators - **Flexible configuration** - Defaults, overrides, test scenarios - **Realistic distributions** - Weighted probabilities and faker data diff --git a/notes/MEDICAL-INFORMATION-GENERATOR-GUIDE.md b/notes/MEDICAL-INFORMATION-GENERATOR-GUIDE.md index 704e4326..458f9a5a 100644 --- a/notes/MEDICAL-INFORMATION-GENERATOR-GUIDE.md +++ b/notes/MEDICAL-INFORMATION-GENERATOR-GUIDE.md @@ -297,12 +297,14 @@ string // e.g., 'Takes warfarin for atrial fibrillation. Last INR check was two ## Medical History Types Status **Implemented (4/7):** + - ✅ Breast cancer - ✅ Implanted medical device - ✅ Breast implants/augmentation - ✅ Mastectomy/lumpectomy **To implement (3/7):** + - Cysts - Benign lumps - Other procedures @@ -412,6 +414,7 @@ if (isCompleted(eventStatus)) { ``` **Key improvements:** + - ✅ All medical information attributed to `sessionDetails.startedBy` (user who ran the appointment) - ✅ Default probabilities set only in umbrella generator (single source of truth) - ✅ Supports config overrides for test scenarios @@ -531,6 +534,7 @@ module.exports = { ``` **Key points:** + - ✅ Default probabilities set here (single source of truth) - ✅ Passes `addedByUserId` to all sub-generators - ✅ Medical info generators organized in `medical-information/` subfolder @@ -541,17 +545,20 @@ module.exports = { **File:** `app/lib/generators/medical-information/medical-history-generator.js` **Implemented types:** + - ✅ Breast cancer (can have multiple) - ✅ Implanted medical device (can have multiple) - ✅ Breast implants/augmentation (single entry, includes consent) - ✅ Mastectomy/lumpectomy (can have multiple) **Remaining types:** + - Cysts (single entry only) - Benign lumps (can have multiple) - Other procedures (can have multiple) **Notes:** + - All field names corrected to match forms (`year` not `procedureYear`, `location` not `treatmentLocation`) - Currently set to 100% probability for all types (testing mode) - Each type has independent probability check allowing multiple types per event diff --git a/notes/previous-mammograms-edit-delete.md b/notes/previous-mammograms-edit-delete.md index e94e45a5..a7b9b07c 100644 --- a/notes/previous-mammograms-edit-delete.md +++ b/notes/previous-mammograms-edit-delete.md @@ -1,11 +1,13 @@ # Previous Mammograms - Add Edit/Delete Functionality ## Overview + Implement add/edit/delete functionality for previous mammograms following the established patterns used in symptoms and medical history. ## Task List ### 1. Data Structure Changes + **File:** `app/routes/events.js` (lines 396-527) - [x] Add unique ID generation using `generateId()` when creating new mammograms @@ -14,6 +16,7 @@ Implement add/edit/delete functionality for previous mammograms following the es - [x] Detect new vs edit using `!previousMammogramTemp.id` ### 2. Update Save Route Logic + **File:** `app/routes/events.js` (lines 396-527) - [x] Replace `.push()` with findIndex/update or push pattern @@ -21,6 +24,7 @@ Implement add/edit/delete functionality for previous mammograms following the es - [x] Ensure all special flows (recent mammogram warning, proceed anyway, end immediately) still work with new structure ### 3. Add Edit Route + **File:** `app/routes/events.js` (add after line 527) - [x] Add GET route: `/clinics/:clinicId/events/:eventId/previous-mammograms/edit/:mammogramId` @@ -30,6 +34,7 @@ Implement add/edit/delete functionality for previous mammograms following the es - [x] Handle case where mammogram not found ### 4. Add Delete Route + **File:** `app/routes/events.js` (add after edit route) - [x] Add GET route: `/clinics/:clinicId/events/:eventId/previous-mammograms/delete/:mammogramId` @@ -38,6 +43,7 @@ Implement add/edit/delete functionality for previous mammograms following the es - [x] Redirect back using referrerChain and scrollTo ### 5. Update Edit Form + **File:** `app/views/events/previous-mammograms/edit.html` - [x] Add delete link at bottom of form @@ -46,6 +52,7 @@ Implement add/edit/delete functionality for previous mammograms following the es - [x] Include referrerChain and scrollTo in delete href ### 6. Update Display Template + **File:** `app/views/_includes/summary-lists/rows/last-known-mammogram.njk` - [x] Separate display into two sections (system record vs user-added) @@ -57,6 +64,7 @@ Implement add/edit/delete functionality for previous mammograms following the es ## Testing Checklist After implementation: + - [ ] Can add a new previous mammogram - [ ] Can edit an existing user-added mammogram - [ ] Can delete a user-added mammogram From 0a455c20eb3ac962a0454a3785c64cb8485d8869 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Mon, 9 Mar 2026 14:44:56 +0000 Subject: [PATCH 4/5] Fix Stylelint formatting --- app/assets/sass/_misc.scss | 28 ++-- app/assets/sass/_typography.scss | 10 +- app/assets/sass/_utils.scss | 2 +- app/assets/sass/_workflow.scss | 42 +++-- app/assets/sass/components/_annotation.scss | 40 +++-- .../sass/components/_appointment-header.scss | 4 +- .../sass/components/_breast-features.scss | 144 ++++++++++++------ app/assets/sass/components/_button-menu.scss | 11 +- app/assets/sass/components/_checkboxes.scss | 4 +- app/assets/sass/components/_count.scss | 10 +- app/assets/sass/components/_dark-mode.scss | 59 +++---- app/assets/sass/components/_environment.scss | 2 +- app/assets/sass/components/_forward-link.scss | 21 ++- app/assets/sass/components/_list-border.scss | 2 +- app/assets/sass/components/_modal.scss | 11 +- app/assets/sass/components/_reading.scss | 101 +++++++----- app/assets/sass/components/_related-nav.scss | 2 +- .../_secondary-navigation-overrides.scss | 4 +- .../components/_secondary-navigation.scss | 4 +- app/assets/sass/components/_status-bar.scss | 7 +- app/assets/sass/components/_status.scss | 3 +- .../sass/components/_stepper-input.scss | 11 +- .../components/_sticky-appointment-bar.scss | 13 +- app/assets/sass/components/_summary-card.scss | 7 +- 24 files changed, 343 insertions(+), 199 deletions(-) diff --git a/app/assets/sass/_misc.scss b/app/assets/sass/_misc.scss index 8fac3e55..d195b59f 100644 --- a/app/assets/sass/_misc.scss +++ b/app/assets/sass/_misc.scss @@ -1,31 +1,35 @@ @use "nhsuk-frontend/dist/nhsuk/core" as *; .app-mammogram-image--placeholder { - background-color: black; width: 125px; // height: 150px; outline: 1px solid rgba(255, 255, 255, 0.2); + background-color: black; p { - color: white; margin-left: 5px; + color: white; } } .app-mammogram-image--missing { - background-color: nhsuk-colour("grey-5"); display: flex; + align-items: center; justify-content: center; + width: 125px; height: 187.5px; + border: 2px dashed nhsuk-colour("grey-2"); + + background-color: nhsuk-colour("grey-5"); } .app-mammogram-image--missing-content { - text-align: center; font-size: 16px; + text-align: center; } // Progressive enhancement helpers @@ -63,13 +67,13 @@ body.js-enabled .app-no-js-only { } .app-suppress-link-styles * { - text-decoration: none; color: $nhsuk-secondary-text-colour; + text-decoration: none; } .app-suppress-link-styles .app-button-link { - text-decoration: none; color: $nhsuk-secondary-text-colour; + text-decoration: none; } .app-image-flip-horizontal { @@ -104,14 +108,18 @@ body.js-enabled .app-no-js-only { // Button styled as a link - for inline form submissions .app-link-button { - background: none; - border: none; padding: 0; + + border: none; + color: $nhsuk-link-colour; + background: none; + + font-family: inherit; + font-size: inherit; text-decoration: underline; + cursor: pointer; - font-size: inherit; - font-family: inherit; &:hover { color: $nhsuk-link-hover-colour; diff --git a/app/assets/sass/_typography.scss b/app/assets/sass/_typography.scss index 93b30dba..e631576c 100644 --- a/app/assets/sass/_typography.scss +++ b/app/assets/sass/_typography.scss @@ -38,15 +38,19 @@ h3 { } .app-button-link { - background: none; + padding: 0; + border: none; + color: $nhsuk-link-colour; - cursor: pointer; + background: none; + font: inherit; - padding: 0; text-align: left; text-decoration: underline; + cursor: pointer; + &:hover, &:focus { color: $nhsuk-link-hover-colour; diff --git a/app/assets/sass/_utils.scss b/app/assets/sass/_utils.scss index 00cef5bc..c680e40b 100644 --- a/app/assets/sass/_utils.scss +++ b/app/assets/sass/_utils.scss @@ -10,8 +10,8 @@ } .app-image-card { - opacity: 1; transition: opacity 0.5s ease-in; + opacity: 1; } .app-display-none { diff --git a/app/assets/sass/_workflow.scss b/app/assets/sass/_workflow.scss index b6436919..c0798006 100644 --- a/app/assets/sass/_workflow.scss +++ b/app/assets/sass/_workflow.scss @@ -41,26 +41,31 @@ } .app-workflow-side-nav__list { - list-style: none; margin: 0; padding: 0; + list-style: none; } .app-workflow-side-nav__item { position: relative; - border-left: 4px solid transparent; margin-left: -1px; + border-left: 4px solid transparent; } .app-workflow-side-nav__link { @include nhsuk-font($size: 16); display: flex; + + position: relative; + align-items: center; - gap: nhsuk-spacing(2); + padding: nhsuk-spacing(2) 0 nhsuk-spacing(2) nhsuk-spacing(2); + text-decoration: none; - position: relative; + + gap: nhsuk-spacing(2); } // Only apply link styles to actual links @@ -82,34 +87,39 @@ a.app-workflow-side-nav__link { } .app-workflow-side-nav__link--disabled { + pointer-events: none; color: $nhsuk-secondary-text-colour; cursor: not-allowed; - pointer-events: none; } // Clickable links (not current, not completed, not disabled) have blue circles .app-workflow-side-nav__link--clickable { .app-workflow-side-nav__number { - background-color: nhsuk-colour("blue"); color: nhsuk-colour("white"); + background-color: nhsuk-colour("blue"); } } .app-workflow-side-nav__number { display: flex; + + flex: 0 0 24px; align-items: center; justify-content: center; - flex: 0 0 24px; + width: 24px; height: 24px; - background-color: nhsuk-colour("grey-3"); - color: nhsuk-colour("grey-1"); + padding-left: 1px; // Nudge numbers slightly right + border-radius: 50%; - font-weight: 600; + + color: nhsuk-colour("grey-1"); + background-color: nhsuk-colour("grey-3"); + font-size: 14px; - text-align: center; + font-weight: 600; line-height: 1; - padding-left: 1px; // Nudge numbers slightly right + text-align: center; .app-icon--tick { width: 16px; @@ -140,24 +150,24 @@ a.app-workflow-side-nav__link { } .app-workflow-side-nav__number { - background-color: nhsuk-colour("blue"); color: nhsuk-colour("white"); + background-color: nhsuk-colour("blue"); } } // Clickable links (not current, not completed, not disabled) have blue circles .app-workflow-side-nav__link--clickable { .app-workflow-side-nav__number { - background-color: nhsuk-colour("blue"); color: nhsuk-colour("white"); + background-color: nhsuk-colour("blue"); } } .app-workflow-side-nav__item--completed { .app-workflow-side-nav__link--clickable .app-workflow-side-nav__number, .app-workflow-side-nav__number { - background-color: nhsuk-colour("green"); color: nhsuk-colour("white"); + background-color: nhsuk-colour("green"); .app-icon--tick path { stroke: nhsuk-colour("white"); @@ -168,8 +178,8 @@ a.app-workflow-side-nav__link { .app-workflow-side-nav__item--disabled { .app-workflow-side-nav__link--clickable .app-workflow-side-nav__number, .app-workflow-side-nav__number { - background-color: nhsuk-colour("grey-4"); color: nhsuk-colour("grey-1"); + background-color: nhsuk-colour("grey-4"); } } diff --git a/app/assets/sass/components/_annotation.scss b/app/assets/sass/components/_annotation.scss index 791db27c..df3658f5 100644 --- a/app/assets/sass/components/_annotation.scss +++ b/app/assets/sass/components/_annotation.scss @@ -2,51 +2,63 @@ @use "nhsuk-frontend/dist/nhsuk/core" as *; .app-mammogram-image { - cursor: crosshair; - max-width: 100%; display: block; + max-width: 100%; + cursor: crosshair; } .app-mammogram-container { - position: relative; display: inline-block; + position: relative; margin-bottom: 1rem; } .app-mammogram-marker { position: absolute; + // filter: drop-shadow(0 0 1px rgba(255, 255, 255, 0.8)) drop-shadow(0 0 2px rgba(255, 255, 255, 0.4)); + z-index: 10; + width: 45px; height: 45px; - border-radius: 50%; + + transform: translate(-50%, -50%); + border: 5px solid $nhsuk-focus-colour; + border-radius: 50%; + + pointer-events: auto; + background-color: transparent; - transform: translate(-50%, -50%); - // filter: drop-shadow(0 0 1px rgba(255, 255, 255, 0.8)) drop-shadow(0 0 2px rgba(255, 255, 255, 0.4)); - z-index: 10; + cursor: grab; - pointer-events: auto; } .app-mammogram-marker__badge { + display: flex; + position: absolute; - left: -22px; top: -22px; + left: -22px; + + align-items: center; + justify-content: center; + width: 22px; height: 22px; + border-radius: 4px; - background-color: $nhsuk-focus-colour; + color: $nhsuk-text-colour; + background-color: $nhsuk-focus-colour; + font-size: 14px; font-weight: bold; - display: flex; - align-items: center; - justify-content: center; line-height: 1; } /* Visual feedback when dragging */ .app-mammogram-marker.dragging { - cursor: grabbing; // filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 6px rgba(255, 255, 255, 0.5)); transform: translate(-50%, -50%) scale(1.1); + cursor: grabbing; } diff --git a/app/assets/sass/components/_appointment-header.scss b/app/assets/sass/components/_appointment-header.scss index 7f0aab0e..5dc133e0 100644 --- a/app/assets/sass/components/_appointment-header.scss +++ b/app/assets/sass/components/_appointment-header.scss @@ -4,8 +4,8 @@ .app-appointment-header--sticky { position: sticky; - top: 0; z-index: 100; + top: 0; border-bottom: 1px solid $nhsuk-border-colour; background-color: $nhsuk-body-background-colour; @@ -26,8 +26,8 @@ } .app-secondary-navigation__link { - font-size: 16px; padding: 8px 0; + font-size: 16px; &:first-child { padding-left: 0; diff --git a/app/assets/sass/components/_breast-features.scss b/app/assets/sass/components/_breast-features.scss index 0b137cfd..b896e4c8 100644 --- a/app/assets/sass/components/_breast-features.scss +++ b/app/assets/sass/components/_breast-features.scss @@ -19,21 +19,30 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: Side labels above the diagram .breast-features__side-labels { display: flex; + justify-content: space-between; + margin-bottom: $breast-features-spacing-small; - font-weight: 600; - font-size: 19px; + color: $nhsuk-text-colour; + + font-size: 19px; + font-weight: 600; } // ELEMENT: SVG diagram container .breast-features__diagram { position: relative; + + margin: 0; + + overflow: hidden; + border: 3px solid $nhsuk-text-colour; + background: nhsuk-colour("white"); - margin: 0; + cursor: crosshair; - overflow: hidden; } // MODIFIER: Read-only diagram @@ -43,44 +52,57 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: SVG element itself (targets the SVG inside the diagram container) .breast-features__diagram svg { + display: block; + width: 100%; height: auto; - aspect-ratio: 2 / 1; - cursor: inherit; + pointer-events: all; - display: block; + + cursor: inherit; + + aspect-ratio: 2 / 1; } // ELEMENT: Interactive markers on the diagram .breast-features__marker { + display: flex; + position: absolute; + z-index: $breast-features-z-index-marker; + + align-items: center; + justify-content: center; + width: $breast-features-marker-size; height: $breast-features-marker-size; - background-color: $nhsuk-link-colour; + border: $breast-features-border-width solid nhsuk-colour("white"); border-radius: $breast-features-border-radius; - z-index: $breast-features-z-index-marker; + pointer-events: all; - display: flex; - align-items: center; - justify-content: center; + color: nhsuk-colour("white"); + background-color: $nhsuk-link-colour; + font-size: $breast-features-marker-font-size; font-weight: bold; - cursor: pointer; - user-select: none; -webkit-user-select: none; -moz-user-select: none; + user-select: none; + + cursor: pointer; + -webkit-touch-callout: none; -webkit-tap-highlight-color: transparent; } // MODIFIER: Dragging marker state .breast-features__marker--dragging { - cursor: grabbing; - opacity: 0.8; transform: scale(1.05); + opacity: 0.8; box-shadow: 0 4px 12px rgba(33, 43, 50, 0.3); + cursor: grabbing; } // MODIFIER: Read-only marker @@ -90,21 +112,26 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: Text labels next to markers .breast-features__marker-label { - background: nhsuk-colour("white"); - border: $breast-features-border-width solid $nhsuk-text-colour; + position: absolute; + z-index: 100; + top: -10px; + left: 45px; + padding: 4px $breast-features-spacing-small; + + border: $breast-features-border-width solid $nhsuk-text-colour; + border-radius: $breast-features-border-radius; + + color: $nhsuk-text-colour; + background: nhsuk-colour("white"); + font-size: 14px; font-weight: 600; - color: $nhsuk-text-colour; - position: absolute; - white-space: nowrap; - z-index: 100; line-height: 1.2; - cursor: grab; + white-space: nowrap; user-select: none; - border-radius: $breast-features-border-radius; - left: 45px; - top: -10px; + + cursor: grab; } .breast-features__marker-label:hover { @@ -113,30 +140,35 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // MODIFIER: Dragging label state .breast-features__marker-label--dragging { - cursor: grabbing; - opacity: 0.8; z-index: $breast-features-z-index-dragging; transform: scale(1.05); + opacity: 0.8; box-shadow: 0 4px 12px rgba(33, 43, 50, 0.3); + cursor: grabbing; } // ELEMENT: Popover for adding/editing features .breast-features__popover { + display: none; + position: fixed; - background: nhsuk-colour("white"); - border: 4px solid $nhsuk-link-colour; - padding: $breast-features-spacing-large; - padding-bottom: calc( - $breast-features-spacing-large - 20px - ); // reduce padding by margin size on buttons. Ideally this would be dynamically generated. - box-shadow: 0 4px 16px rgba(33, 43, 50, 0.16); z-index: $breast-features-z-index-popover; + min-width: $breast-features-popover-min-width; max-width: $breast-features-popover-max-width; - display: none; - border-radius: 4px; max-height: 80vh; + padding: $breast-features-spacing-large; + padding-bottom: calc( + $breast-features-spacing-large - 20px + ); // reduce padding by margin size on buttons. Ideally this would be dynamically generated. + overflow-y: auto; + + border: 4px solid $nhsuk-link-colour; + border-radius: 4px; + + background: nhsuk-colour("white"); + box-shadow: 0 4px 16px rgba(33, 43, 50, 0.16); } // ELEMENT: Features list component @@ -147,8 +179,8 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: List header .breast-features__list-header { display: flex; - justify-content: space-between; align-items: center; + justify-content: space-between; margin-bottom: $breast-features-spacing-medium; } @@ -164,18 +196,22 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: List items container .breast-features__list-items { - list-style: none; - padding: 0; margin: 0; + padding: 0; + list-style: none; } // ELEMENT: Individual feature item in the list .breast-features__item { display: flex; + align-items: center; justify-content: space-between; + padding: 12px 0; + border-bottom: 1px solid $nhsuk-secondary-text-colour; + gap: 12px; } @@ -197,26 +233,32 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: Feature number badge .breast-features__item-number { + display: flex; + position: relative; + + flex-shrink: 0; + align-items: center; + justify-content: center; + width: $breast-features-marker-size; height: $breast-features-marker-size; - flex-shrink: 0; - background-color: $nhsuk-link-colour; + border: $breast-features-border-width solid nhsuk-colour("white"); border-radius: $breast-features-border-radius; - display: flex; - align-items: center; - justify-content: center; + color: nhsuk-colour("white"); + background-color: $nhsuk-link-colour; + font-size: $breast-features-marker-font-size; font-weight: bold; } // ELEMENT: Feature text label .breast-features__item-label { - font-weight: 600; color: $nhsuk-text-colour; font-size: 16px; + font-weight: 600; } // ELEMENT: Feature position/location @@ -228,8 +270,8 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: Anatomical region container (from processed SVG) .app-breast-diagram__regions { - opacity: 0; pointer-events: all; + opacity: 0; } // MODIFIER: Visible regions @@ -239,12 +281,14 @@ $breast-features-z-index-dragging: 60; // Still below sticky nav but above norma // ELEMENT: Individual anatomical region (from processed SVG) .app-breast-diagram__region { + pointer-events: all; + opacity: 0; + + fill: none; + stroke: $nhsuk-secondary-text-colour; stroke-width: 1px; stroke-dasharray: 5, 5; - fill: none; - opacity: 0; - pointer-events: all; } // When regions are visible, show them diff --git a/app/assets/sass/components/_button-menu.scss b/app/assets/sass/components/_button-menu.scss index 455b4f2c..4a03048e 100644 --- a/app/assets/sass/components/_button-menu.scss +++ b/app/assets/sass/components/_button-menu.scss @@ -31,13 +31,17 @@ .app-button-menu__wrapper { position: absolute; z-index: 10; + width: 300px; margin: 5px 0 0; // 2px shadow, 3px gap padding: 0; + list-style: none; - background-color: nhsuk-colour("white"); + border: 1px solid $nhsuk-border-colour; border-bottom: nhsuk-spacing(1) solid $nhsuk-border-colour; + + background-color: nhsuk-colour("white"); // box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); @include nhsuk-print-hide; @@ -67,7 +71,9 @@ .app-button-menu li > .app-button-menu__item { box-sizing: border-box; display: inline-block; + position: relative; + width: 100%; margin-top: 0; margin-right: 0; @@ -82,9 +88,12 @@ // color: $nhsuk-text-colour; color: nhsuk-colour("blue"); background-color: nhsuk-colour("white"); + text-align: left; vertical-align: top; + cursor: pointer; + -webkit-appearance: none; appearance: none; @include nhsuk-font(19); diff --git a/app/assets/sass/components/_checkboxes.scss b/app/assets/sass/components/_checkboxes.scss index bfbb5cd7..1dcc252b 100644 --- a/app/assets/sass/components/_checkboxes.scss +++ b/app/assets/sass/components/_checkboxes.scss @@ -5,9 +5,9 @@ @include nhsuk-clearfix; .nhsuk-checkboxes__item { - clear: none; - float: left; margin-right: nhsuk-spacing(4); + float: left; + clear: none; } } diff --git a/app/assets/sass/components/_count.scss b/app/assets/sass/components/_count.scss index 5d6d9c78..f611befb 100644 --- a/app/assets/sass/components/_count.scss +++ b/app/assets/sass/components/_count.scss @@ -1,12 +1,16 @@ @use "nhsuk-frontend/dist/nhsuk/core" as *; .app-count { - background-color: rgba(nhsuk-colour("grey-4"), 0.5); - border-radius: nhsuk-spacing(3); display: inline-block; + min-width: nhsuk-spacing(5); - padding-left: nhsuk-spacing(2); padding-right: nhsuk-spacing(2); + padding-left: nhsuk-spacing(2); + + border-radius: nhsuk-spacing(3); + + background-color: rgba(nhsuk-colour("grey-4"), 0.5); + text-align: center; @include nhsuk-font(16); } diff --git a/app/assets/sass/components/_dark-mode.scss b/app/assets/sass/components/_dark-mode.scss index b5635969..790d0ae6 100644 --- a/app/assets/sass/components/_dark-mode.scss +++ b/app/assets/sass/components/_dark-mode.scss @@ -13,7 +13,7 @@ $app-dark-mode-banner-border: #015eb8; // Links $app-dark-mode-link-colour: #3892e0; $app-dark-mode-link-hover-colour: #ffeb3b; -$app-dark-mode-link-active-colour: #000; +$app-dark-mode-link-active-colour: #000000; $app-dark-mode-link-visited-colour: #d055a5; // Tags @@ -34,15 +34,15 @@ $app-tag--blue-dark-border: nhsuk-tint(nhsuk-colour("blue"), 80%); $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); @mixin app-dark-mode-focused-text { - background-color: $nhsuk-focus-colour; - box-shadow: - 0 -2px $nhsuk-focus-colour, - 0 $nhsuk-focus-width $nhsuk-focus-text-colour; - color: $nhsuk-focus-text-colour; // When colours are overridden, for example when users have a dark mode, // backgrounds and box-shadows disappear, so we need to ensure there's a // transparent outline which will be set to a visible colour. outline: $nhsuk-focus-width solid transparent; + color: $nhsuk-focus-text-colour; + background-color: $nhsuk-focus-colour; + box-shadow: + 0 -2px $nhsuk-focus-colour, + 0 $nhsuk-focus-width $nhsuk-focus-text-colour; // When link is focussed, hide the default underline since the // box shadow adds the "underline" text-decoration: none; @@ -61,7 +61,7 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } &:focus { - @include nhsuk-focused-text(); + @include nhsuk-focused-text; &:hover { text-decoration: none; @@ -82,8 +82,8 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } .app-dark-mode { - background: $app-dark-mode-background; color: $app-dark-mode-text-colour; + background: $app-dark-mode-background; // Text colour .nhsuk-fieldset__legend { @@ -113,6 +113,7 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); .app-secondary-navigation__link:link { color: $app-dark-mode-link-colour; + &:focus { @include app-dark-mode-focused-text; } @@ -129,12 +130,12 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); .app-secondary-navigation__link:focus, .app-secondary-navigation__link:focus:hover { + outline: $nhsuk-focus-width solid transparent !important; color: $nhsuk-focus-text-colour !important; background-color: $nhsuk-focus-colour !important; box-shadow: 0 -2px $nhsuk-focus-colour, 0 $nhsuk-focus-width $nhsuk-focus-text-colour !important; - outline: $nhsuk-focus-width solid transparent !important; text-decoration: none !important; } @@ -151,18 +152,19 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } &:focus { + outline: $nhsuk-focus-width solid transparent !important; color: $nhsuk-focus-text-colour !important; background-color: $nhsuk-focus-colour !important; box-shadow: 0 -2px $nhsuk-focus-colour, 0 $nhsuk-focus-width $nhsuk-focus-text-colour !important; - outline: $nhsuk-focus-width solid transparent !important; text-decoration: none !important; } } .nhsuk-link--no-visited-state { color: $app-dark-mode-link-colour; + &:focus { @include app-dark-mode-focused-text; } @@ -191,8 +193,8 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } .nhsuk-button--secondary { - box-shadow: 0 4px 0 $app-dark-mode-link-colour; color: $app-dark-mode-link-colour; + box-shadow: 0 4px 0 $app-dark-mode-link-colour; &:link, &:visited, @@ -201,9 +203,9 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } &:hover { + color: $app-dark-mode-link-colour; background-color: $app-dark-mode-surface; box-shadow: 0 4px 0 $app-dark-mode-link-colour; - color: $app-dark-mode-link-colour; } } @@ -214,6 +216,7 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); &:visited { color: #ffffff; } + &:focus { @include app-dark-mode-focused-text; } @@ -229,38 +232,38 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } .app-count { - background: $app-dark-mode-background-secondary; color: $app-dark-mode-text-colour; + background: $app-dark-mode-background-secondary; } .nhsuk-tag--red { - background-color: $app-tag--red-dark-background; border-color: $app-tag--red-dark-border; color: $app-tag--red-dark-text; + background-color: $app-tag--red-dark-background; } .nhsuk-tag--green { - background-color: $app-tag--green-dark-background; border-color: $app-tag--green-dark-border; color: $app-tag--green-dark-text; + background-color: $app-tag--green-dark-background; } .nhsuk-tag--orange { - background-color: $app-tag--orange-dark-background; border-color: $app-tag--orange-dark-border; color: $app-tag--orange-dark-text; + background-color: $app-tag--orange-dark-background; } .nhsuk-tag--yellow { - background-color: $app-tag--yellow-dark-background; border-color: $app-tag--yellow-dark-border; color: $app-tag--yellow-dark-text; + background-color: $app-tag--yellow-dark-background; } .nhsuk-tag--blue { - background-color: $app-tag--blue-dark-background; border-color: $app-tag--blue-dark-border; color: $app-tag--blue-dark-text; + background-color: $app-tag--blue-dark-background; } .nhsuk-error-summary__title { @@ -268,9 +271,9 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } // Component overrides .nhsuk-card { - background: $app-dark-mode-surface; border: 1px solid $app-dark-mode-border; color: $app-dark-mode-text-colour; + background: $app-dark-mode-surface; .nhsuk-card__heading--feature { // background: $app-dark-mode-blue; @@ -280,8 +283,8 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); .nhsuk-footer, .nhsuk-footer-container { - background: $app-dark-mode-surface; color: $app-dark-mode-text-colour; + background: $app-dark-mode-surface; .nhsuk-footer__list-item-link, .nhsuk-footer__copyright { @@ -294,17 +297,19 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); border-color: $app-dark-mode-white; background-color: transparent; } + .nhsuk-radios__input + .nhsuk-radios__label::after { - background: $app-dark-mode-white; border: 10px solid $app-dark-mode-white; + background: $app-dark-mode-white; } } .nhsuk-checkboxes { .nhsuk-checkboxes__input + .nhsuk-checkboxes__label::before { - background: transparent; border-color: $app-dark-mode-white; + background: transparent; } + .nhsuk-checkboxes__input + .nhsuk-checkboxes__label::after { // Checkmark styling } @@ -318,9 +323,9 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); .nhsuk-input, .nhsuk-textarea, .nhsuk-select { - background-color: transparent; border-color: $app-dark-mode-white; color: $app-dark-mode-text-colour; + background-color: transparent; } .nhsuk-notification-banner__content { @@ -333,8 +338,8 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } .app-reading-opinion-banner { - background: $app-dark-mode-surface; border: 2px solid $app-dark-mode-banner-border; + background: $app-dark-mode-surface; filter: drop-shadow(2px 3px 3px $app-dark-mode-surface); } @@ -346,8 +351,8 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } .app-mammogram-thumbnail__missing { - background-color: $app-dark-mode-surface; border-color: $app-dark-mode-text-colour-secondary; + background-color: $app-dark-mode-surface; } .app-mammogram-thumbnail__missing-text { @@ -356,7 +361,7 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); } html:has(.app-dark-mode) { - background-color: $app-dark-mode-background-secondary; - background: $app-dark-mode-background-secondary; color: $app-dark-mode-text-colour; + background: $app-dark-mode-background-secondary; + background-color: $app-dark-mode-background-secondary; } diff --git a/app/assets/sass/components/_environment.scss b/app/assets/sass/components/_environment.scss index 153adb49..312f88f1 100644 --- a/app/assets/sass/components/_environment.scss +++ b/app/assets/sass/components/_environment.scss @@ -13,9 +13,9 @@ } .nhsuk-width-container { - align-items: baseline; display: flex; flex-wrap: wrap; + align-items: baseline; gap: nhsuk-spacing(1) nhsuk-spacing(2); } } diff --git a/app/assets/sass/components/_forward-link.scss b/app/assets/sass/components/_forward-link.scss index ef867a02..94fe78ec 100644 --- a/app/assets/sass/components/_forward-link.scss +++ b/app/assets/sass/components/_forward-link.scss @@ -13,13 +13,18 @@ $_font-size: 16; .nhsuk-forward-link { - background: none; - border: 0; - cursor: pointer; display: inline-block; + + position: relative; + padding: 0; padding-right: nhsuk-em($_font-size, $nhsuk-base-font-size); - position: relative; + + border: 0; + + background: none; + + cursor: pointer; @include nhsuk-font($_font-size); @include nhsuk-link-style-default; @@ -28,12 +33,14 @@ @include nhsuk-print-hide; // [2] &::before { - bottom: 0; content: ""; - right: 0; - margin: auto; + position: absolute; top: 0; + right: 0; + bottom: 0; + + margin: auto; @include nhsuk-shape-chevron(right, $colour: currentcolor, $font-size: 19); } diff --git a/app/assets/sass/components/_list-border.scss b/app/assets/sass/components/_list-border.scss index 1adeb530..5615d50d 100644 --- a/app/assets/sass/components/_list-border.scss +++ b/app/assets/sass/components/_list-border.scss @@ -5,6 +5,6 @@ // ========================================================================== .nhsuk-list--border li { - border-bottom: 1px solid $nhsuk-border-colour; padding: 8px 0 16px; + border-bottom: 1px solid $nhsuk-border-colour; } diff --git a/app/assets/sass/components/_modal.scss b/app/assets/sass/components/_modal.scss index f2bf0cb6..848051d7 100644 --- a/app/assets/sass/components/_modal.scss +++ b/app/assets/sass/components/_modal.scss @@ -4,8 +4,8 @@ $app-modal-width: 700px !default; // Default width for the modal // Prevent background scrolling when modal is open body.app-modal-open { - overflow: hidden; width: 100%; + overflow: hidden; } .app-modal { @@ -24,8 +24,10 @@ body.app-modal-open { z-index: 1001; top: 0; left: 0; + width: 100%; height: 100%; + background-color: rgba(nhsuk-colour("black"), 0.8); } @@ -35,16 +37,19 @@ body.app-modal-open { z-index: 1002; top: 50%; left: 50%; + width: calc(100vw - #{nhsuk-spacing(4)}); max-width: $app-modal-width; max-height: 90vh; - transform: translate(-50%, -50%); overflow-y: auto; + transform: translate(-50%, -50%); + + border: $nhsuk-focus-width solid nhsuk-colour("black"); + // background-color: nhsuk-colour("white"); background-color: $nhsuk-body-background-colour; - border: $nhsuk-focus-width solid nhsuk-colour("black"); @include nhsuk-responsive-padding(5); diff --git a/app/assets/sass/components/_reading.scss b/app/assets/sass/components/_reading.scss index f37d54d1..2fab79d7 100644 --- a/app/assets/sass/components/_reading.scss +++ b/app/assets/sass/components/_reading.scss @@ -3,12 +3,13 @@ // app/assets/sass/components/_reading.scss .app-reading-status { - background-color: nhsuk-shade(nhsuk-colour("blue"), 40%); + padding: 10px; color: $nhsuk-reverse-text-colour; + background-color: nhsuk-shade(nhsuk-colour("blue"), 40%); + a { color: $nhsuk-reverse-text-colour; } - padding: 10px; } // Mammogram thumbnail grid for reading pages @@ -34,15 +35,19 @@ // Individual image wrapper (contains image + label) .app-mammogram-thumbnail__image-wrapper { + // Use flexbox for image alignment + display: flex; + position: relative; + + align-items: flex-start; // Fixed dimensions for consistent sizing width: 120px; height: 150px; - background-color: #000; + overflow: hidden; - // Use flexbox for image alignment - display: flex; - align-items: flex-start; + + background-color: #000000; } // Right breast images align right (inwards), left breast align left (inwards) @@ -55,9 +60,9 @@ } .app-mammogram-thumbnail__image { + display: block; max-width: 100%; max-height: 100%; - display: block; cursor: default; } @@ -68,13 +73,16 @@ .app-mammogram-thumbnail__label { position: absolute; + z-index: 1; top: 4px; - background-color: nhsuk-colour("blue"); - color: #fff; + padding: 2px 6px; + + color: #ffffff; + background-color: nhsuk-colour("blue"); + font-size: 11px; font-weight: 600; - z-index: 1; } // Labels align to outer edge - right breast labels on left, left breast labels on right @@ -88,33 +96,41 @@ // Missing image placeholder .app-mammogram-thumbnail__missing { + box-sizing: border-box; + display: flex; + + align-items: center; + justify-content: center; + width: 100%; height: 100%; + border: 2px dashed nhsuk-colour("grey-2"); + background-color: nhsuk-colour("grey-5"); - display: flex; - align-items: center; - justify-content: center; - box-sizing: border-box; } .app-mammogram-thumbnail__missing-text { - font-size: 12px; color: nhsuk-colour("grey-2"); + font-size: 12px; } // Zoom overlay for thumbnails .app-mammogram-zoom { + display: flex; + position: fixed; + z-index: 9999; top: 0; - left: 0; right: 0; bottom: 0; - background-color: rgba(0, 0, 0, 0.95); - z-index: 9999; - display: flex; + left: 0; + align-items: center; justify-content: center; + + background-color: rgba(0, 0, 0, 0.95); + cursor: default; } @@ -128,9 +144,12 @@ position: absolute; top: 16px; left: 16px; - background-color: nhsuk-colour("blue"); - color: #fff; + padding: 8px 16px; + + color: #ffffff; + background-color: nhsuk-colour("blue"); + font-size: 14px; font-weight: 600; } @@ -139,10 +158,10 @@ // Todo: otherwise refctor so classes aren't created with & .app-reading-status { - background-color: nhsuk-shade(nhsuk-colour("blue"), 40%); - color: $nhsuk-reverse-text-colour; padding: 8px 0; border-bottom: 1px solid #d8dde0; + color: $nhsuk-reverse-text-colour; + background-color: nhsuk-shade(nhsuk-colour("blue"), 40%); &__row { display: flex; @@ -176,31 +195,37 @@ align-items: center; strong { - font-weight: 600; opacity: 0.9; + font-weight: 600; } } } // Toast message shown after giving an opinion .app-reading-opinion-banner { - background-color: nhsuk-colour("blue"); - color: $nhsuk-reverse-text-colour; display: flex; - gap: nhsuk-spacing(3); - opacity: 1; - transition: opacity 0.2s ease-out; + + position: absolute; + z-index: 10; + top: 10px; // Manually chosen to line up with the 'previous case' link left: 17px; - top: 10px; + max-width: 720px; - padding: nhsuk-spacing(3) nhsuk-spacing(3); - position: absolute; - z-index: 10; + padding: nhsuk-spacing(3); + + transition: opacity 0.2s ease-out; // Todo: reinstate this in v11 // border-radius: nhsuk-px-to-rem($nhsuk-button-border-radius); border-radius: 4px; + + opacity: 1; + + color: $nhsuk-reverse-text-colour; + background-color: nhsuk-colour("blue"); + + gap: nhsuk-spacing(3); filter: drop-shadow(2px 3px 3px nhsuk-tint(nhsuk-colour("black"), 50%)); } @@ -215,18 +240,18 @@ } .app-reading-opinion-banner__text { - color: $nhsuk-reverse-text-colour; margin: 0; + color: $nhsuk-reverse-text-colour; } .app-reading-opinion-banner__link, .app-reading-opinion-banner__link:visited, .app-reading-opinion-banner__link:hover, .app-reading-opinion-banner__link:focus { - color: $nhsuk-reverse-text-colour; margin-left: nhsuk-spacing(3); - text-decoration: underline; transform: translateY(2px); + color: $nhsuk-reverse-text-colour; + text-decoration: underline; white-space: nowrap; } @@ -264,8 +289,8 @@ button, input[type="button"], input[type="submit"] { - opacity: 0.6; pointer-events: none; + opacity: 0.6; } .app-button-link, @@ -288,6 +313,6 @@ // Keyboard shortcut hint styling .app-shortcut-hint { - font-weight: 400; opacity: 0.8; + font-weight: 400; } diff --git a/app/assets/sass/components/_related-nav.scss b/app/assets/sass/components/_related-nav.scss index 4ba1b69b..4ab1ce44 100755 --- a/app/assets/sass/components/_related-nav.scss +++ b/app/assets/sass/components/_related-nav.scss @@ -19,7 +19,7 @@ } .nhsuk-related-nav__list { - list-style: none; padding-left: 0; + list-style: none; @include nhsuk-font-size(16); } diff --git a/app/assets/sass/components/_secondary-navigation-overrides.scss b/app/assets/sass/components/_secondary-navigation-overrides.scss index ac61e6af..cb1ccf6f 100644 --- a/app/assets/sass/components/_secondary-navigation-overrides.scss +++ b/app/assets/sass/components/_secondary-navigation-overrides.scss @@ -9,10 +9,10 @@ // Style the tick SVG specifically .app-secondary-navigation .app-icon--tick { - vertical-align: middle; + flex-shrink: 0; // Prevent SVG from shrinking width: 20px; height: 20px; - flex-shrink: 0; // Prevent SVG from shrinking + vertical-align: middle; // Style the tick path path { diff --git a/app/assets/sass/components/_secondary-navigation.scss b/app/assets/sass/components/_secondary-navigation.scss index 345b6c02..75ff03d8 100644 --- a/app/assets/sass/components/_secondary-navigation.scss +++ b/app/assets/sass/components/_secondary-navigation.scss @@ -54,10 +54,10 @@ } .app-secondary-navigation__link--disabled { - color: $nhsuk-secondary-text-colour; - cursor: not-allowed; pointer-events: none; + color: $nhsuk-secondary-text-colour; text-decoration: none; + cursor: not-allowed; &:hover, &:focus { diff --git a/app/assets/sass/components/_status-bar.scss b/app/assets/sass/components/_status-bar.scss index 480c19c8..836d47d7 100644 --- a/app/assets/sass/components/_status-bar.scss +++ b/app/assets/sass/components/_status-bar.scss @@ -3,10 +3,10 @@ // app/assets/sass/components/_reading.scss .app-status-bar { - background-color: nhsuk-shade($nhsuk-brand-colour, 40%); - color: $nhsuk-reverse-text-colour; padding: 8px 0; border-bottom: 1px solid #d8dde0; + color: $nhsuk-reverse-text-colour; + background-color: nhsuk-shade($nhsuk-brand-colour, 40%); } .app-status-bar a { @@ -16,6 +16,7 @@ &:active { color: $nhsuk-reverse-text-colour; } + &:focus { color: $nhsuk-text-colour; } @@ -52,8 +53,8 @@ is-sticky { display: block; position: sticky; - top: 0; z-index: 999; + top: 0; } // Sass variables for header height calculations diff --git a/app/assets/sass/components/_status.scss b/app/assets/sass/components/_status.scss index 9063b597..ed1555ac 100644 --- a/app/assets/sass/components/_status.scss +++ b/app/assets/sass/components/_status.scss @@ -6,8 +6,9 @@ .app-header-with-status__status-tag { position: absolute; top: 10px; - right: 0px; + right: 0; text-align: right; + .app-modal { text-align: initial; } diff --git a/app/assets/sass/components/_stepper-input.scss b/app/assets/sass/components/_stepper-input.scss index 7bd1553b..0398fb62 100644 --- a/app/assets/sass/components/_stepper-input.scss +++ b/app/assets/sass/components/_stepper-input.scss @@ -6,20 +6,23 @@ .app-stepper-input__wrapper { display: flex; + flex-wrap: nowrap; align-items: stretch; gap: 8px; - flex-wrap: nowrap; } .app-stepper-input__button { - margin-bottom: 4px !important; - flex-shrink: 0; - align-self: stretch; display: flex; + + flex-shrink: 0; align-items: center; + align-self: stretch; justify-content: center; + width: auto !important; min-width: 0 !important; + margin-bottom: 4px !important; + user-select: none; // Ensure focus state is visible above input diff --git a/app/assets/sass/components/_sticky-appointment-bar.scss b/app/assets/sass/components/_sticky-appointment-bar.scss index 4632f8fc..69ae831d 100644 --- a/app/assets/sass/components/_sticky-appointment-bar.scss +++ b/app/assets/sass/components/_sticky-appointment-bar.scss @@ -1,7 +1,7 @@ .app-sidebar-sticky { position: sticky; - top: 20px; z-index: 100; + top: 20px; height: fit-content; } @@ -20,13 +20,16 @@ .app-sticky-bottom-container { position: fixed; + z-index: 200; + right: 0; bottom: 0; left: 0; - right: 0; - z-index: 200; - background: #ffffff; - border-top: 1px solid #d8dde0; + padding: 20px; + + border-top: 1px solid #d8dde0; + + background: #ffffff; box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1); } diff --git a/app/assets/sass/components/_summary-card.scss b/app/assets/sass/components/_summary-card.scss index c3fe7607..817d4b50 100644 --- a/app/assets/sass/components/_summary-card.scss +++ b/app/assets/sass/components/_summary-card.scss @@ -63,8 +63,8 @@ .nhsuk-summary-card__title { @include nhsuk-font($size: 19, $weight: bold); - color: $nhsuk-text-colour; margin: nhsuk-spacing(1) nhsuk-spacing(4) nhsuk-spacing(2) 0; + color: $nhsuk-text-colour; @include nhsuk-media-query($from: "tablet") { margin-bottom: nhsuk-spacing(1); @@ -75,16 +75,19 @@ @include nhsuk-font-size($size: 19); @include nhsuk-typography-weight-bold; display: flex; + flex-wrap: wrap; row-gap: 10px; + margin: nhsuk-spacing(1) 0; padding: 0; + list-style: none; @include nhsuk-media-query($from: "tablet") { justify-content: right; - text-align: right; margin-left: auto; // Added so actions are always aligned right + text-align: right; } } From 22cb368394fc1572489b888686d748b0bcca0665 Mon Sep 17 00:00:00 2001 From: Colin Rotherham Date: Thu, 19 Feb 2026 14:11:19 +0000 Subject: [PATCH 5/5] Fix Stylelint issues --- app/assets/sass/_misc.scss | 8 ++--- app/assets/sass/_typography.scss | 2 +- app/assets/sass/_workflow.scss | 10 ++---- app/assets/sass/components/_annotation.scss | 5 +-- app/assets/sass/components/_button-menu.scss | 7 ++-- app/assets/sass/components/_dark-mode.scss | 4 ++- app/assets/sass/components/_modal.scss | 1 + app/assets/sass/components/_reading.scss | 14 +++----- .../_secondary-navigation-overrides.scss | 3 +- .../sass/components/_stepper-input.scss | 3 ++ .../components/_sticky-appointment-bar.scss | 4 +-- app/assets/sass/components/_summary-card.scss | 32 ++++++++----------- 12 files changed, 43 insertions(+), 50 deletions(-) diff --git a/app/assets/sass/_misc.scss b/app/assets/sass/_misc.scss index d195b59f..a3686bcb 100644 --- a/app/assets/sass/_misc.scss +++ b/app/assets/sass/_misc.scss @@ -5,11 +5,11 @@ // height: 150px; outline: 1px solid rgba(255, 255, 255, 0.2); - background-color: black; + background-color: #000000; p { margin-left: 5px; - color: white; + color: #ffffff; } } @@ -37,11 +37,11 @@ display: none; } -body.js-enabled .app-js-only { +.nhsuk-frontend-supported .app-js-only { display: block; } -body.js-enabled .app-no-js-only { +.nhsuk-frontend-supported .app-no-js-only { display: none; } diff --git a/app/assets/sass/_typography.scss b/app/assets/sass/_typography.scss index e631576c..c55ed2e7 100644 --- a/app/assets/sass/_typography.scss +++ b/app/assets/sass/_typography.scss @@ -17,7 +17,7 @@ h3 { } .nhsuk-link--no-visited-state a { - @extend .nhsuk-link--no-visited-state; + @include nhsuk-link-style-no-visited-state; } .app-link--error, diff --git a/app/assets/sass/_workflow.scss b/app/assets/sass/_workflow.scss index c0798006..d02fef14 100644 --- a/app/assets/sass/_workflow.scss +++ b/app/assets/sass/_workflow.scss @@ -69,11 +69,13 @@ } // Only apply link styles to actual links +// stylelint-disable-next-line selector-no-qualifying-type a.app-workflow-side-nav__link { @include nhsuk-link-style-default; @include nhsuk-link-style-no-visited-state; &:hover { + // stylelint-disable-next-line selector-no-qualifying-type .app-workflow-side-nav__label { text-decoration: underline; text-decoration-thickness: max(3px, 0.1875rem, 0.12em); @@ -92,14 +94,6 @@ a.app-workflow-side-nav__link { cursor: not-allowed; } -// Clickable links (not current, not completed, not disabled) have blue circles -.app-workflow-side-nav__link--clickable { - .app-workflow-side-nav__number { - color: nhsuk-colour("white"); - background-color: nhsuk-colour("blue"); - } -} - .app-workflow-side-nav__number { display: flex; diff --git a/app/assets/sass/components/_annotation.scss b/app/assets/sass/components/_annotation.scss index df3658f5..d6e81a61 100644 --- a/app/assets/sass/components/_annotation.scss +++ b/app/assets/sass/components/_annotation.scss @@ -1,6 +1,7 @@ -/* Mammogram annotation styles */ @use "nhsuk-frontend/dist/nhsuk/core" as *; +// Mammogram annotation styles + .app-mammogram-image { display: block; max-width: 100%; @@ -56,7 +57,7 @@ line-height: 1; } -/* Visual feedback when dragging */ +// Visual feedback when dragging .app-mammogram-marker.dragging { // filter: drop-shadow(0 0 3px rgba(255, 255, 255, 0.9)) drop-shadow(0 0 6px rgba(255, 255, 255, 0.5)); transform: translate(-50%, -50%) scale(1.1); diff --git a/app/assets/sass/components/_button-menu.scss b/app/assets/sass/components/_button-menu.scss index 4a03048e..5aad5e9b 100644 --- a/app/assets/sass/components/_button-menu.scss +++ b/app/assets/sass/components/_button-menu.scss @@ -1,6 +1,7 @@ +@use "nhsuk-frontend/dist/nhsuk/core" as *; + // Button Menu Component // Adapted from MOJ Frontend for NHS prototype use -@use "nhsuk-frontend/dist/nhsuk/core" as *; .app-button-menu { display: inline-block; @@ -50,7 +51,7 @@ } } -/* Menu items with no JS */ +// Menu items with no JS .app-button-menu__item { display: inline-block; width: auto; // Override NHS UK's 100% width @@ -67,7 +68,7 @@ margin: 0; } -/* Menu items with JS */ +// Menu items with JS .app-button-menu li > .app-button-menu__item { box-sizing: border-box; display: inline-block; diff --git a/app/assets/sass/components/_dark-mode.scss b/app/assets/sass/components/_dark-mode.scss index 790d0ae6..c6092c85 100644 --- a/app/assets/sass/components/_dark-mode.scss +++ b/app/assets/sass/components/_dark-mode.scss @@ -1,3 +1,5 @@ +// stylelint-disable declaration-no-important + @use "nhsuk-frontend/dist/nhsuk/core" as *; $app-dark-mode-blue: #348de0; @@ -188,7 +190,7 @@ $app-tag--blue-dark-text: nhsuk-tint(nhsuk-colour("blue"), 80%); &:visited, &:active, &:hover { - color: white; + color: #ffffff; } } diff --git a/app/assets/sass/components/_modal.scss b/app/assets/sass/components/_modal.scss index 848051d7..03cfa296 100644 --- a/app/assets/sass/components/_modal.scss +++ b/app/assets/sass/components/_modal.scss @@ -3,6 +3,7 @@ $app-modal-width: 700px !default; // Default width for the modal // Prevent background scrolling when modal is open +// stylelint-disable-next-line selector-no-qualifying-type body.app-modal-open { width: 100%; overflow: hidden; diff --git a/app/assets/sass/components/_reading.scss b/app/assets/sass/components/_reading.scss index 2fab79d7..b2eff040 100644 --- a/app/assets/sass/components/_reading.scss +++ b/app/assets/sass/components/_reading.scss @@ -2,16 +2,6 @@ // app/assets/sass/components/_reading.scss -.app-reading-status { - padding: 10px; - color: $nhsuk-reverse-text-colour; - background-color: nhsuk-shade(nhsuk-colour("blue"), 40%); - - a { - color: $nhsuk-reverse-text-colour; - } -} - // Mammogram thumbnail grid for reading pages .app-mammogram-thumbnails { display: inline-grid; @@ -163,6 +153,10 @@ color: $nhsuk-reverse-text-colour; background-color: nhsuk-shade(nhsuk-colour("blue"), 40%); + a { + color: $nhsuk-reverse-text-colour; + } + &__row { display: flex; flex-wrap: wrap; diff --git a/app/assets/sass/components/_secondary-navigation-overrides.scss b/app/assets/sass/components/_secondary-navigation-overrides.scss index cb1ccf6f..2d809654 100644 --- a/app/assets/sass/components/_secondary-navigation-overrides.scss +++ b/app/assets/sass/components/_secondary-navigation-overrides.scss @@ -1,6 +1,7 @@ -// app/assets/sass/components/_secondary-navigation-overrides.scss @use "nhsuk-frontend/dist/nhsuk/core" as *; +// app/assets/sass/components/_secondary-navigation-overrides.scss + // Fix for tick SVG in secondary navigation .app-secondary-navigation__link { // gap is already set to 6px in the main component diff --git a/app/assets/sass/components/_stepper-input.scss b/app/assets/sass/components/_stepper-input.scss index 0398fb62..6c32c26e 100644 --- a/app/assets/sass/components/_stepper-input.scss +++ b/app/assets/sass/components/_stepper-input.scss @@ -1,3 +1,5 @@ +// stylelint-disable declaration-no-important + // app/assets/sass/components/_stepper-input.scss .app-stepper-input { @@ -44,6 +46,7 @@ } } +// stylelint-disable-next-line selector-no-qualifying-type .app-stepper-input input.nhsuk-input { text-align: center; } diff --git a/app/assets/sass/components/_sticky-appointment-bar.scss b/app/assets/sass/components/_sticky-appointment-bar.scss index 69ae831d..2d137d62 100644 --- a/app/assets/sass/components/_sticky-appointment-bar.scss +++ b/app/assets/sass/components/_sticky-appointment-bar.scss @@ -16,7 +16,7 @@ box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); } -/* Sticky action button */ +// Sticky action button .app-sticky-bottom-container { position: fixed; @@ -39,6 +39,6 @@ } .app-sticky-bottom-button { - /* Use standard NHS button styling */ + // Use standard NHS button styling margin: 0; } diff --git a/app/assets/sass/components/_summary-card.scss b/app/assets/sass/components/_summary-card.scss index 817d4b50..49de7e04 100644 --- a/app/assets/sass/components/_summary-card.scss +++ b/app/assets/sass/components/_summary-card.scss @@ -4,7 +4,20 @@ // https://github.com/alphagov/govuk-frontend/blob/main/packages/govuk-frontend/src/govuk/components/summary-list/_index.scss .nhsuk-summary-card__action { - display: inline-block; + display: inline; + + // We use the following media query to target IE11 and 10 only to add margin + // between actions. + // + // We do this because we're using row-gap to create space between actions on + // more evergreen browsers which IE doesn't support. @supports currently isn't + // a viable solution, see https://github.com/w3c/csswg-drafts/issues/3559. + // + // Solution taken from https://stackoverflow.com/questions/11173106/apply-style-only-on-ie#answer-36448860 + // which also includes an explanation of why this works + @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { + margin-bottom: nhsuk-spacing(1); + } } @include nhsuk-media-query($until: tablet) { @@ -91,23 +104,6 @@ } } -.nhsuk-summary-card__action { - display: inline; - - // We use the following media query to target IE11 and 10 only to add margin - // between actions. - // - // We do this because we're using row-gap to create space between actions on - // more evergreen browsers which IE doesn't support. @supports currently isn't - // a viable solution, see https://github.com/w3c/csswg-drafts/issues/3559. - // - // Solution taken from https://stackoverflow.com/questions/11173106/apply-style-only-on-ie#answer-36448860 - // which also includes an explanation of why this works - @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - margin-bottom: nhsuk-spacing(1); - } -} - .nhsuk-summary-card__action:last-child { // See above comment for why this is here @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {