From 1a6bda00b857ee33ed634d728a0ed911164388fb Mon Sep 17 00:00:00 2001 From: Ben De St Paer-Gotch Date: Thu, 1 Oct 2026 14:44:53 +0000 Subject: [PATCH 01/26] Update supported regions for Azure VNet (#63636) --- data/reusables/actions/azure-vnet-supported-regions.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/data/reusables/actions/azure-vnet-supported-regions.md b/data/reusables/actions/azure-vnet-supported-regions.md index 854542dd9549..bd9b6060a826 100644 --- a/data/reusables/actions/azure-vnet-supported-regions.md +++ b/data/reusables/actions/azure-vnet-supported-regions.md @@ -14,11 +14,9 @@ The following regions are supported on {% data variables.product.prodname_dotcom
  • EastUs
  • EastUs2
  • FranceCentral
  • -
  • GermanyWestCentral
  • JapanWest
  • KoreaCentral
  • NorthCentralUs
  • -
  • NorthEurope
  • NorwayEast
  • SouthCentralUs
  • SoutheastAsia
  • From a07d75c8d4e97398083429ffac772e7012a9656d Mon Sep 17 00:00:00 2001 From: Sunbrye Ly <56200261+sunbrye@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:59:19 +0000 Subject: [PATCH 02/26] Add Claude 5.5 models to data residency regions (#63626) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../admin/data-residency/github-copilot-with-data-residency.md | 2 ++ data/reusables/copilot/model-compliance/us-models.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/content/admin/data-residency/github-copilot-with-data-residency.md b/content/admin/data-residency/github-copilot-with-data-residency.md index cd8001e1ebac..410ebc2814a9 100644 --- a/content/admin/data-residency/github-copilot-with-data-residency.md +++ b/content/admin/data-residency/github-copilot-with-data-residency.md @@ -69,7 +69,9 @@ The models available for {% data variables.product.prodname_copilot_short %} var * {% data variables.copilot.copilot_claude_opus_47 %} * {% data variables.copilot.copilot_claude_opus_48 %} * {% data variables.copilot.copilot_claude_opus_5 %} +* {% data variables.copilot.copilot_claude_opus_55 %} * {% data variables.copilot.copilot_claude_sonnet_5 %} +* {% data variables.copilot.copilot_claude_sonnet_55 %} * {% data variables.copilot.copilot_gemini_35_flash %} ## Pricing changes diff --git a/data/reusables/copilot/model-compliance/us-models.md b/data/reusables/copilot/model-compliance/us-models.md index d0fa75bac1b8..58ab00ff56d6 100644 --- a/data/reusables/copilot/model-compliance/us-models.md +++ b/data/reusables/copilot/model-compliance/us-models.md @@ -6,5 +6,7 @@ * GPT-5.3-Codex * Claude Haiku 4.5 * Claude Sonnet 5 +* Claude Sonnet 5.5 * Claude Opus 4.8 * Claude Opus 5 +* Claude Opus 5.5 From 146a3c931e24649dde0294176cad7718e7dd83a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Oct 2026 15:21:44 +0000 Subject: [PATCH 03/26] Bump the npm_and_yarn group across 1 directory with 4 updates (#63618) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package-lock.json | 144 +++++++++++++++++++++++----------------------- package.json | 4 +- 2 files changed, 74 insertions(+), 74 deletions(-) diff --git a/package-lock.json b/package-lock.json index 26cee73d6447..3ed24cad3e95 100644 --- a/package-lock.json +++ b/package-lock.json @@ -64,7 +64,7 @@ "mdast-util-to-hast": "^13.2.1", "mdast-util-to-markdown": "2.1.2", "mdast-util-to-string": "^4.0.0", - "next": "^16.3.3", + "next": "^16.3.6", "parse5": "8.0.1", "quick-lru": "7.0.1", "react": "^19.2.5", @@ -898,9 +898,9 @@ } }, "node_modules/@eslint/config-array/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -989,9 +989,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -2182,15 +2182,15 @@ } }, "node_modules/@next/env": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.3.tgz", - "integrity": "sha512-U2eYQRwXj+dsqxV79zFqExDdatnNY/ZWc2nsJU1p/OgT7fd3dXwlF6OjYaFQCfMoeTA19PWq+wVmYgimVA+V+g==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.3.6.tgz", + "integrity": "sha512-x9Vblze1EbtltQYnNH38xCPWU3TVfBd1eXqA3+w9+BTpedkkdNpAaltXlGQ/nsc1+E0mVTNrtcbX3GoO09zeLQ==", "license": "MIT" }, "node_modules/@next/swc-darwin-arm64": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.3.tgz", - "integrity": "sha512-8Hiv32QJPwdV6KYJ8meR9SBA061tQqnIKTJDocvOXlEQqib0xMFpzArosuffFUUc0sslbh7QQ8a3Yey1QV8EIw==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.3.6.tgz", + "integrity": "sha512-E/7GEqaUkt8mk/T8v9lAnrhzR06kdq1ZBkC12F8tAMkdIadwNp3H1KqHynDHrpcTlGCUdq/qu6vUL2aYVyYBdw==", "cpu": [ "arm64" ], @@ -2204,9 +2204,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.3.tgz", - "integrity": "sha512-A1lgKgwVchRYmSe467zdwhxT9040dd8lH+o65sL5Jet8fjB4kegw/rDyPIpYVRb6jAqwXFOJpjIXJLxQKLiE3A==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.3.6.tgz", + "integrity": "sha512-yBE893/nDWTlaiBD1p+qgt7NUen4U5R6FXyH0s67Npq1S3E0cVSef1WIXC2xBRgQvwAvJq6DnS6Y6PrY0cy4Ew==", "cpu": [ "x64" ], @@ -2220,9 +2220,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.3.tgz", - "integrity": "sha512-bf0FIssMFueU2dm7vQEWWxk0c8UjKTdW0yzuh0sQsD8pf1+KCLDdaqhYZNMYGmXwEOiHAUzgBKudovIlcvvBjg==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.3.6.tgz", + "integrity": "sha512-KJDpjBqBPYlvkivmyrp+Qys6k/7ksbqGQvRVc6ZEGfR+cjQxx+nUkJaWmNZJsmoOrqYNbaXByF8wa0lBwDhB3Q==", "cpu": [ "arm64" ], @@ -2239,9 +2239,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.3.tgz", - "integrity": "sha512-W7viwCk9JY/cAkdz/A273rd5bb3RgT/IHwR7Upv90tunjBWNtAAhGhoecHh+teRNRSinuAFmE+l7fwZ4YKkrXg==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.3.6.tgz", + "integrity": "sha512-mqNg2K+hvWskSRb/QM+Ix412DvBsuSF0XV+frTSw5vmoucNnIlynFwKYew8D01bfATErMOM7Bujrf0BA5DRKFA==", "cpu": [ "arm64" ], @@ -2258,9 +2258,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.3.tgz", - "integrity": "sha512-0W46zw1N3ODpI6n0GeivHvvob1pooozgZVqy65k0mh4/7vr+FbY9+WpHzNVXjHipJf/A3FDheBG19H1s5A25rA==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.3.6.tgz", + "integrity": "sha512-nFncBNGAYouRHjRVaITs9beZRfhX4ssVwpnvPIAbkZVH6LtGoAVlH4bJ8Cnf9SOo9bsXgPFer/GdHtEE3JNOkw==", "cpu": [ "x64" ], @@ -2277,9 +2277,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.3.tgz", - "integrity": "sha512-H4mBso8ZTMBPtdT0PN0pBx2ayTvQuTuvS6qT13d77yVFJXAPCxkyIhLTmdMaGTJs0krQYI/qpzdHijCeihXhbg==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.3.6.tgz", + "integrity": "sha512-5Mf3cHDGR/Iz0ng2Bj3zUR3p5QS9YK3Hn2QiAfavFmyF48zwThAjpFoiTKNIcOHLYS4zEk+gzyJ/9deQ2ZB8yQ==", "cpu": [ "x64" ], @@ -2296,9 +2296,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.3.tgz", - "integrity": "sha512-cTMUJpcEGmeywofCUfhR+rSsoE33+rVPnPEYNTNdLNlsOeEg/vktOsKUSTb28vUGqD2jkm4Zaskcwn7OCI6FQg==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.3.6.tgz", + "integrity": "sha512-0jkJy0C2kbrJWTk4YLa3xk80pVBpx8FCHJym7CnUfDAXe/FWv5qT7SQJbR0KuemyxaEDlEx5WT4VQJoTW+/9Qw==", "cpu": [ "arm64" ], @@ -2312,9 +2312,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.3.tgz", - "integrity": "sha512-2VR4cTBzHXaBjnGsuH6GyJjENzQOmHeAh11uY1iUhjm3j5dEUrVJuUj+VL78jaGi/Dik8xS76zEj18BsFhlVZQ==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.3.6.tgz", + "integrity": "sha512-/YXjI1e5OXcZ7YpxRwgP/1jAV/SBKTzeVKqN2mk7mLpcICsyn3Gl5+dIfDTJp70M0ccMhyMMRso4v6mPDCGepg==", "cpu": [ "x64" ], @@ -5776,14 +5776,14 @@ } }, "node_modules/axios": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.18.1.tgz", - "integrity": "sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.20.0.tgz", + "integrity": "sha512-r8aOh8j9cGKpgQAqpzrUHnSIc6a59Y3Xf/cv8sy1DrHCkZHzQGEuoq1tARk6qSyDdtQGSDgpb9kFlruzPvrgwg==", "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", + "form-data": "^4.0.6", "https-proxy-agent": "^5.0.1", "proxy-from-env": "^2.1.0" } @@ -6096,9 +6096,9 @@ } }, "node_modules/brace-expansion": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", - "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -7979,9 +7979,9 @@ } }, "node_modules/eslint-plugin-import/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -8053,9 +8053,9 @@ } }, "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -8199,9 +8199,9 @@ } }, "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -8598,9 +8598,9 @@ "dev": true }, "node_modules/fast-uri": { - "version": "3.1.7", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.7.tgz", - "integrity": "sha512-dOvZVzjdZdz7phd9v6jCbwxrBW3fK6n8Rc0CtdmM4bumzMnxywBYhuph6J819RRw/ku+rLbelwfMunktuzVVHg==", + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.8.tgz", + "integrity": "sha512-GZMtZUTNRpOVIECoXwLNZS5xUGE+mVNbTB8h/7Rwh2TFWcBQiPzTgyZi05BF9UMZKkLJv8XBRJTlU7zg8+ZfMg==", "funding": [ { "type": "github", @@ -11148,9 +11148,9 @@ } }, "node_modules/matcher-collection/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -12196,12 +12196,12 @@ } }, "node_modules/next": { - "version": "16.3.3", - "resolved": "https://registry.npmjs.org/next/-/next-16.3.3.tgz", - "integrity": "sha512-tuRTx1nQ/yVw83cwJBo9F+njGUgMn3UHQycreWHB8XsStvvAh1AthbI8/4IpKnFaF58F+iSiHejYOlMQ/eq83g==", + "version": "16.3.6", + "resolved": "https://registry.npmjs.org/next/-/next-16.3.6.tgz", + "integrity": "sha512-L+otWM/aQbYTx98aZhgEoMb4bZAXx1YVW4UMA/vuCyCoWG5HJyZUili8QAkqzrcC+5///tsz3s0M+SlyB5bLMw==", "license": "MIT", "dependencies": { - "@next/env": "16.3.3", + "@next/env": "16.3.6", "@swc/helpers": "0.5.23", "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", @@ -12215,15 +12215,15 @@ "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "16.3.3", - "@next/swc-darwin-x64": "16.3.3", - "@next/swc-linux-arm64-gnu": "16.3.3", - "@next/swc-linux-arm64-musl": "16.3.3", - "@next/swc-linux-x64-gnu": "16.3.3", - "@next/swc-linux-x64-musl": "16.3.3", - "@next/swc-win32-arm64-msvc": "16.3.3", - "@next/swc-win32-x64-msvc": "16.3.3", - "sharp": "^0.35.3" + "@next/swc-darwin-arm64": "16.3.6", + "@next/swc-darwin-x64": "16.3.6", + "@next/swc-linux-arm64-gnu": "16.3.6", + "@next/swc-linux-arm64-musl": "16.3.6", + "@next/swc-linux-x64-gnu": "16.3.6", + "@next/swc-linux-x64-musl": "16.3.6", + "@next/swc-win32-arm64-msvc": "16.3.6", + "@next/swc-win32-x64-msvc": "16.3.6", + "sharp": "^0.35.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -12310,9 +12310,9 @@ } }, "node_modules/nodemon/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index b3dbd41c47a5..b176ae0f75f9 100644 --- a/package.json +++ b/package.json @@ -223,7 +223,7 @@ "mdast-util-to-hast": "^13.2.1", "mdast-util-to-markdown": "2.1.2", "mdast-util-to-string": "^4.0.0", - "next": "^16.3.3", + "next": "^16.3.6", "parse5": "8.0.1", "quick-lru": "7.0.1", "react": "^19.2.5", @@ -356,7 +356,7 @@ "brace-expansion": "^5.0.8" }, "sharp": "$sharp", - "fast-uri": "^3.1.7" + "fast-uri": "^3.1.8" }, "engines": { "node": "^24 || ^26" From b11b35efb6c3cc368136b33f3d6f77aba689dd25 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 1 Oct 2026 15:58:41 +0000 Subject: [PATCH 04/26] Stop declaring bogus build args on the Dockerfile APP_HOME line (#63417) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7c52b57f-2c29-4aa1-8233-99d0d6e13571 --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1d49692f2726..94766ce91d10 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,8 +30,8 @@ RUN --mount=type=secret,id=apt-auth-conf,target=/etc/apt/auth.conf.d/apt_auth.co && apt-get install -y nodejs \ && node --version -# Create the node user and home directory -ARG APP_HOME="/home/node/app" # Define in base so all child stages inherit it +# Stages built FROM base inherit this ARG, so every later stage can use APP_HOME. +ARG APP_HOME="/home/node/app" RUN useradd -ms /bin/bash node \ && mkdir -p $APP_HOME && chown -R node:node $APP_HOME From c67a9362ec63f3993d095bf70bd11724359cc50e Mon Sep 17 00:00:00 2001 From: subatoi <32935794+subatoi@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:03:10 +0000 Subject: [PATCH 05/26] Document on.workflow_run.workflows (#63634) Co-authored-by: Enrico Minack --- .../workflows-and-actions/workflow-syntax.md | 6 +++- .../workflows/section-specifying-workflows.md | 31 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) create mode 100644 data/reusables/actions/workflows/section-specifying-workflows.md diff --git a/content/actions/reference/workflows-and-actions/workflow-syntax.md b/content/actions/reference/workflows-and-actions/workflow-syntax.md index 5f916e52265e..0953709ba987 100644 --- a/content/actions/reference/workflows-and-actions/workflow-syntax.md +++ b/content/actions/reference/workflows-and-actions/workflow-syntax.md @@ -313,6 +313,10 @@ A boolean specifying whether the secret must be supplied. {% data reusables.actions.workflows.section-specifying-branches %} +## `on.workflow_run.workflows` + +{% data reusables.actions.workflows.section-specifying-workflows %} + ## `on.workflow_dispatch` {% data reusables.actions.workflow-dispatch %} @@ -1639,7 +1643,7 @@ Allowed expression contexts: `github`, `needs`, and `secrets`. ## Filter pattern cheat sheet -You can use special characters in path, branch, and tag filters. +You can use special characters in path, branch, tag, and workflow name filters. * `*`: Matches zero or more characters, but does not match the `/` character. For example, `Octo*` matches `Octocat`. * `**`: Matches zero or more of any character. diff --git a/data/reusables/actions/workflows/section-specifying-workflows.md b/data/reusables/actions/workflows/section-specifying-workflows.md new file mode 100644 index 000000000000..8c5ab4183f76 --- /dev/null +++ b/data/reusables/actions/workflows/section-specifying-workflows.md @@ -0,0 +1,31 @@ + +When using the `workflow_run` event, you can specify which workflows can trigger your workflow. + +The `workflows` filters accept glob patterns that use characters like `*`, `**`, `+`, `?`, `!` and others to match more than one workflow name. If a name contains any of these characters and you want a literal match, you need to _escape_ each of these special characters with `\`. For more information about glob patterns, see the [AUTOTITLE](/actions/writing-workflows/workflow-syntax-for-github-actions#filter-pattern-cheat-sheet). + +For example, a workflow with the following trigger will only run when the workflow named `Build` runs: + +```yaml +on: + workflow_run: + workflows: ["Build"] + types: [requested] +``` + +A workflow with the following trigger will only run when a workflow whose name starts with `Build` completed: + +```yaml +on: + workflow_run: + workflows: ["Build*"] + types: [completed] +``` + +A workflow with the following trigger will only run when the workflow named `Build C++` completed: + +```yaml +on: + workflow_run: + workflows: ["Build C\\+\\+"] + types: [completed] +``` From b93b236956414b7e26afdc35869f95d93b8429b7 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 1 Oct 2026 16:04:01 +0000 Subject: [PATCH 06/26] Tighten code comments in REST example and body-param scripts (#63418) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7c52b57f-2c29-4aa1-8233-99d0d6e13571 Copilot-Session: 03e47b55-eabf-42a3-86cc-8d79e869cb1f --- .../scripts/utils/create-rest-examples.ts | 125 ++++-------------- src/rest/scripts/utils/get-body-params.ts | 61 +++------ .../utils/tests/get-body-params.test.ts | 17 +-- 3 files changed, 46 insertions(+), 157 deletions(-) diff --git a/src/rest/scripts/utils/create-rest-examples.ts b/src/rest/scripts/utils/create-rest-examples.ts index 00d2d3bb173d..6a94996a55a6 100644 --- a/src/rest/scripts/utils/create-rest-examples.ts +++ b/src/rest/scripts/utils/create-rest-examples.ts @@ -1,16 +1,12 @@ import type { OpenApiMediaType } from './openapi-types' -// In the case that there are more than one example requests, and -// no content responses, a request with an example key that matches the -// status code of a response will be matched. const DEFAULT_EXAMPLE_DESCRIPTION = 'Example' const DEFAULT_EXAMPLE_KEY = 'default' const DEFAULT_ACCEPT_HEADER = 'application/vnd.github.v3+json' -// These functions only read the request body, parameters, and responses of an -// operation, so they accept a narrower shape than the full OpenApiOperation. -// Content maps are typed as `unknown` values (cast to OpenApiMediaType at the -// point of use) so the partial operation fixtures in tests remain assignable. +// These helpers accept partial operation shapes because they read only request bodies, +// parameters, and responses. Unknown content maps keep test fixtures assignable until +// each use casts the value to OpenApiMediaType. interface CodeSampleParameter { in?: string name: string @@ -70,10 +66,7 @@ export interface MergedExample { } } -// Retrieves request and response examples and attempts to -// merge them to create matching request/response examples -// The key used in the media type `examples` property is -// used to match requests to responses. +// getCodeSamples builds request and response examples, then applies merge rules before rendering. export default async function getCodeSamples( operation: CodeSampleOperation, ): Promise { @@ -82,8 +75,7 @@ export default async function getCodeSamples( const mergedExamples = mergeExamples(requestExamples, responseExamples) - // If there are multiple examples and if the request body - // has the same description, add a number to the example + // Duplicate descriptions get status-code suffixes so each docs example has a distinct label. if (mergedExamples.length > 1) { const count: Record = {} for (const item of mergedExamples) { @@ -110,28 +102,23 @@ export default async function getCodeSamples( return mergedExamples } +// mergeExamples applies direct, status-code, and example-key rules to pair requests with responses. +// If earlier rules do not apply, the fallback path matches request and response example keys. export function mergeExamples( requestExamples: RequestExample[], responseExamples: ResponseExample[], ): MergedExample[] { - // There is always at least one request example, but it won't create - // a meaningful example unless it has a response example. + // A lone request without a response cannot create a meaningful docs example. if (requestExamples.length === 1 && responseExamples.length === 0) { return [] } - // If there is one request and one response example, we don't - // need to merge the requests and responses, and we don't need - // to match keys directly. This allows falling back in the - // case that the existing OpenAPI schema has mismatched example keys. + // A single request and response pair directly, so mismatched OpenAPI example keys still render. if (requestExamples.length === 1 && responseExamples.length === 1) { return [{ ...requestExamples[0], response: responseExamples[0].response }] } - // If there is a request with no request body parameters and all of - // the responses have no content, then we can create a docs - // example for just status codes below 300. All other status codes will - // be listed in the status code table in the docs. + // A single request with example-less responses documents success status codes below 300. if ( requestExamples.length === 1 && responseExamples.length > 1 && @@ -142,10 +129,7 @@ export function mergeExamples( .map((ex) => ({ ...requestExamples[0], ...ex })) } - // If there is exactly one request example and one or more response - // examples, we can make a docs example for the response examples that - // have content. All remaining status codes with no content - // will be listed in the status code table in the docs. + // When one request has multiple responses, only responses with examples become docs examples. if ( requestExamples.length === 1 && responseExamples.length > 1 && @@ -156,17 +140,11 @@ export function mergeExamples( .map((ex) => ({ ...requestExamples[0], ...ex })) } - // Finally, we'll attempt to match examples with matching keys. - // This iterates through the longer array and compares key values to keys in - // the shorter array. const requestsExamplesLarger = requestExamples.length >= responseExamples.length const target = requestsExamplesLarger ? requestExamples : responseExamples const source = requestsExamplesLarger ? responseExamples : requestExamples - // Walk the longer array ("target", or the requests when the two are equal - // length) looking for a matching key in the other one ("source"). A request - // and a response with the same key are merged into one example. If several - // keys match, the first one wins. + // The longer list drives key matching. Requests win ties on length, and the first key match wins. return target .filter((targetEx) => { const match = source.find((srcEx) => srcEx.key === targetEx.key) @@ -176,17 +154,12 @@ export function mergeExamples( .map((ex) => ex as MergedExample) } -// Builds request examples from the media types in the operation's requestBody, -// falling back to a path-parameter or generic example when there is no body -// example. Every result has a key plus a request with description and -// acceptHeader; contentType, bodyParameters and parameters are optional. +// Request examples fall back to path parameters or generic examples when bodies lack examples. export function getRequestExamples(operation: CodeSampleOperation): RequestExample[] { const requestExamples: RequestExample[] = [] const parameterExamples = getParameterExamples(operation) - // When no request body or parameters are defined, we create a generic - // request example. Not all operations have request bodies or parameters, - // but we always want to show at least an example with the path. + // Operations without request bodies or path parameters still need a path-only example. if (!operation.requestBody && Object.keys(parameterExamples).length === 0) { return [ { @@ -199,7 +172,7 @@ export function getRequestExamples(operation: CodeSampleOperation): RequestExamp ] } - // When no request body exists, we create an example from the parameters + // Path parameter examples create requests when an operation has no request body. if (!operation.requestBody) { return Object.keys(parameterExamples).map((key) => { return { @@ -213,16 +186,10 @@ export function getRequestExamples(operation: CodeSampleOperation): RequestExamp }) } - // Requests can have multiple content types each with their own set of - // examples. for (const contentType of Object.keys(operation.requestBody.content)) { const mediaType = operation.requestBody.content[contentType] as OpenApiMediaType let examples: Record = {} - // This is a fallback to allow using the `example` property in - // the schema. If we start to enforce using examples vs. example using - // a linter, we can remove the check for `example`. - // For now, we'll use the key default, which is a common default - // example name in the OpenAPI schema. + // Treat a media type with a singular example field as examples under the default key. if (mediaType.example) { examples = { default: { @@ -232,7 +199,7 @@ export function getRequestExamples(operation: CodeSampleOperation): RequestExamp } else if (mediaType.examples) { examples = mediaType.examples } else { - // Example for this content type doesn't exist so we'll try and create one + // Missing media type examples still need a generic request for this content type. requestExamples.push({ key: DEFAULT_EXAMPLE_KEY, request: { @@ -245,13 +212,8 @@ export function getRequestExamples(operation: CodeSampleOperation): RequestExamp continue } - // There can be more than one example for a given content type. We need to - // iterate over the keys of the examples to create individual - // example objects for (const key of Object.keys(examples)) { - // A content type that includes `+json` is a custom media type - // The default accept header is application/vnd.github.v3+json - // Which would have a content type of `application/json` + // Custom +json media types must also become the Accept header. const acceptHeader = contentType.includes('+json') ? contentType : 'application/vnd.github.v3+json' @@ -272,9 +234,7 @@ export function getRequestExamples(operation: CodeSampleOperation): RequestExamp return requestExamples } -// Recursively removes the `example` and `examples` annotation fields from a -// JSON Schema object. Nothing at runtime reads them, and they account for -// ~131 MB of the total schema.json size across all versions. +// Strip unused example annotations because they add about 131 MB across versioned schemas. function stripSchemaExamples(schema: unknown): unknown { if (!schema || typeof schema !== 'object') return schema if (Array.isArray(schema)) return schema.map(stripSchemaExamples) @@ -287,23 +247,17 @@ function stripSchemaExamples(schema: unknown): unknown { return result } -// Builds examples for the operation's responses below status 400. Every result -// has a key plus a response with statusCode and description; contentType, -// example and schema are only present when the media type had an example. export function getResponseExamples(operation: CodeSampleOperation): ResponseExample[] { const responseExamples: ResponseExample[] = [] const responses = operation.responses as Record for (const statusCode of Object.keys(responses)) { - // We don't want to create examples for error codes - // Error codes are displayed in the status table in the docs + // Error responses already render in the docs status code table. if (parseInt(statusCode, 10) >= 400) continue const response = responses[statusCode] const content = response.content as Record | undefined - // A response doesn't always have content (ex:, status 304) - // In this case we create a generic example for the status code - // with a key that matches the status code. + // Responses without content still need a status-code example. if (!content) { const example = { key: statusCode, @@ -316,16 +270,10 @@ export function getResponseExamples(operation: CodeSampleOperation): ResponseExa continue } - // Responses can have multiple content types each with their own set of - // examples. for (const contentType of Object.keys(content)) { const mediaType = content[contentType] as OpenApiMediaType let examples: Record = {} - // This is a fallback to allow using the `example` property in - // the schema. If we start to enforce using examples vs. example using - // a linter, we can remove the check for `example`. - // We key by statusCode so that operations with multiple success - // responses (e.g. 200 + 201) get unique keys instead of colliding. + // Status-code keys prevent collisions for operations with success responses like 200 and 201. if (mediaType.example) { examples = { [statusCode]: { @@ -335,12 +283,7 @@ export function getResponseExamples(operation: CodeSampleOperation): ResponseExa } else if (mediaType.examples) { examples = mediaType.examples } else if (parseInt(statusCode, 10) < 300) { - // Sometimes there are missing examples for say a 200 response and - // the operation also has a 304 no content status. If we don't add - // the 200 response example, even though it has not example response, - // the resulting responseExamples would only contain the 304 response. - // That would be confusing in the docs because it's expected to see the - // common or success responses by default. + // Missing success examples still render so a 304 is not the only default example. const example = { key: statusCode, response: { @@ -351,15 +294,9 @@ export function getResponseExamples(operation: CodeSampleOperation): ResponseExa responseExamples.push(example) continue } else { - // Example for this content type doesn't exist. - // We could also check if there is a fully populated example - // directly in the response schema examples properties. continue } - // There can be more than one example for a given content type. We need to - // iterate over the keys of the examples to create individual - // example objects for (const key of Object.keys(examples)) { const example = { key, @@ -368,10 +305,7 @@ export function getResponseExamples(operation: CodeSampleOperation): ResponseExa contentType, description: examples[key].summary || response.description || '', example: examples[key].value, - // Note: Including the schema significantly increases JSON file size (~4x), - // but it's necessary to support the schema/example toggle in the UI. - // Users can switch between viewing the example response and the full schema. - // example/examples annotation fields are stripped as they are not rendered. + // Schema data makes JSON about 4x larger, but the UI needs the example/schema toggle. schema: stripSchemaExamples(mediaType.schema), }, } @@ -382,12 +316,8 @@ export function getResponseExamples(operation: CodeSampleOperation): ResponseExa return responseExamples } -// Groups the operation's path parameter values by example key, in the shape: -// -// { [example key]: { [parameter name]: value } } -// -// A parameter with no examples contributes its uppercased name under the -// `default` key. +// Path parameter example values are grouped by example key, then parameter name. +// Parameters without examples use uppercased names under default so fake route values stand out. export function getParameterExamples( operation: CodeSampleOperation, ): Record> { @@ -398,9 +328,6 @@ export function getParameterExamples( const parameterExamples: Record> = {} for (const parameter of parameters) { const examples = parameter.examples - // If there are no examples, create an example from the uppercase parameter - // name, so that it is more visible that the value is fake data - // in the route path. if (!examples) { if (!parameterExamples.default) parameterExamples.default = {} parameterExamples.default[parameter.name] = parameter.name.toUpperCase() diff --git a/src/rest/scripts/utils/get-body-params.ts b/src/rest/scripts/utils/get-body-params.ts index e0061f7cf31c..d275af5b7f53 100644 --- a/src/rest/scripts/utils/get-body-params.ts +++ b/src/rest/scripts/utils/get-body-params.ts @@ -32,13 +32,11 @@ interface BodyParamProps { childParamsGroups?: TransformedParam[] } -// If there is a oneOf at the top level, then we have to present just one -// in the docs. We don't currently have a convention for showing more than one -// set of input parameters in the docs. Having a top-level oneOf is also very -// uncommon. -// Currently there aren't very many operations that require this treatment. -// As an example, the 'Add status check contexts' and 'Set status check contexts' -// operations have a top-level oneOf. +// Docs cannot display multiple input parameter sets for top-level oneOf. +// getTopLevelOneOfProperty uses the first option. The Add status check contexts +// and Set status check contexts operations need this. +// When every top-level oneOf option is an object, getTopLevelOneOfProperty merges +// all properties. With three or more options, middle required fields can lose required flags. async function getTopLevelOneOfProperty( schema: Schema, ): Promise<{ properties: Record; required: string[] }> { @@ -49,18 +47,12 @@ async function getTopLevelOneOfProperty( throw new Error('Schema requestBody oneOf property is not an array') } - // When a oneOf exists but the `type` differs, the case has historically - // been that the alternate option is an array, where the first option - // is the array as a property of the object. We need to ensure that the - // first option listed is the most comprehensive and preferred option. + // When oneOf types differ, the first option must be the comprehensive object form. const firstOneOfObject = schema.oneOf[0] const allOneOfAreObjects = schema.oneOf.every((elem) => elem.type === 'object') let required = firstOneOfObject.required || [] let properties = firstOneOfObject.properties || {} - // When all of the oneOf objects have the `type: object` we - // need to display all of the parameters. - // This merges all of the properties and required values. if (allOneOfAreObjects) { required = [] properties = {} @@ -76,7 +68,6 @@ async function getTopLevelOneOfProperty( return { properties, required } } -// Handles a oneOf whose items are all objects. Returns [] for anything else. async function handleObjectOnlyOneOf( param: Schema, paramType: string[], @@ -89,15 +80,19 @@ async function handleObjectOnlyOneOf( return [] } -// Gets the body parameters for a schema, recursively. +// OpenAPI 3.0 allows one type value, while OpenAPI 3.1 also allows an array, so getBodyParams +// normalizes type values to arrays before it builds the rendered type string. +// For child parameters, getBodyParams reads object-valued additionalProperties recursively. +// The Create a snapshot of dependencies for a repository and Update a gist operations need +// that dictionary shape. Object-only oneOf alternatives also recurse into child parameters, +// while mixed oneOf adds types and descriptions without creating child parameter groups. export async function getBodyParams(schema: Schema, topLevel = false): Promise { const bodyParametersParsed: TransformedParam[] = [] const schemaObject = schema.oneOf && topLevel ? await getTopLevelOneOfProperty(schema) : schema const properties = schemaObject.properties || {} const required = schemaObject.required || [] - // Most operation requestBody schemas are objects. When the type is an array, - // there will not be properties on the `schema` object. + // Top-level array schemas have no properties on the schema object. if (topLevel && schema.type === 'array') { const childParamsGroups: TransformedParam[] = [] if (!schema.items) { @@ -118,11 +113,6 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise t !== undefined, ) @@ -134,13 +124,6 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise (item as Schema).type === 'object', @@ -248,7 +226,7 @@ export async function getBodyParams(schema: Schema, topLevel = false): Promise { const { paramKey, required, childParamsGroups } = props const paramDecorated: TransformedParam = {} as TransformedParam - // Supports backwards compatibility for OpenAPI 3.0 - // In 3.1 a nullable type is part of the param.type array and - // the property param.nullable does not exist. + // OpenAPI 3.0 stores nullable separately from OpenAPI 3.1 type arrays. if (param.nullable) paramType.push('null') paramDecorated.type = Array.from(new Set(paramType.filter(Boolean))).join(' or ') paramDecorated.name = paramKey || '' @@ -284,8 +260,7 @@ async function getTransformedParam( paramDecorated.isRequired = true } if (childParamsGroups && childParamsGroups.length > 0 && !param.oneOfObject) { - // allOf can contribute the same property more than once. Drop the - // duplicates by name, keeping whichever one has isRequired set. + // Drop duplicate allOf child params by name, preferring required entries. const mergedChildParamsGroups = Array.from( childParamsGroups .reduce((childParam, obj) => { diff --git a/src/rest/scripts/utils/tests/get-body-params.test.ts b/src/rest/scripts/utils/tests/get-body-params.test.ts index 826ad950bbc9..ba2e1aecba23 100644 --- a/src/rest/scripts/utils/tests/get-body-params.test.ts +++ b/src/rest/scripts/utils/tests/get-body-params.test.ts @@ -2,15 +2,12 @@ import { describe, expect, it, vi } from 'vitest' import { getBodyParams, type Schema } from '@/rest/scripts/utils/get-body-params' -// Mock render-content so tests don't require the full content-render pipeline +// renderContent returns input because these tests cover schema transformation, not rendering. vi.mock('../render-content', () => ({ renderContent: async (template: string) => template, })) describe('getBodyParams — OAS 3.1 nullable handling', () => { - // ── Bug #3 ────────────────────────────────────────────────────────────────── - // anyOf: [{type:"null"}, {type:"object"}] → type should render as "object or null" - it('renders anyOf [{type:"null"},{type:"object"}] as "object or null"', async () => { const schema = { type: 'object', @@ -58,8 +55,7 @@ describe('getBodyParams — OAS 3.1 nullable handling', () => { expect(params[0].type).toBe('object or null') }) - it('renders anyOf [{type:"string"},{type:"null"}] as "string" using the first option fallback', async () => { - // When anyOf has no object, it uses the existing fallback: param.anyOf[0].type + it('renders anyOf [{type:"string"},{type:"null"}] as "string" (no object, falls back to anyOf[0])', async () => { const schema = { type: 'object', properties: { @@ -71,7 +67,6 @@ describe('getBodyParams — OAS 3.1 nullable handling', () => { const params = await getBodyParams(schema, false) expect(params).toHaveLength(1) expect(params[0].name).toBe('label') - // No object found in anyOf → falls back to anyOf[0].type = 'string' expect(params[0].type).toBe('string') }) @@ -109,10 +104,6 @@ describe('getBodyParams — OAS 3.1 nullable handling', () => { ]) }) - // ── OAS 3.1 type: ["string", "null"] scalar ───────────────────────────── - // This is already handled by existing code (paramType array normalization). - // These tests verify the existing OAS 3.1 scalar nullable path still works. - it('renders type: ["string", "null"] as "string or null"', async () => { const schema = { type: 'object', @@ -144,7 +135,6 @@ describe('getBodyParams — OAS 3.1 nullable handling', () => { expect(params[0].type).toBe('integer or null') }) - // ── anyOf without null ──────────────────────────────────────────────────── it('renders anyOf [{type:"object"}] without null (no hasNull) as just "object"', async () => { const schema = { type: 'object', @@ -165,11 +155,9 @@ describe('getBodyParams — OAS 3.1 nullable handling', () => { const params = await getBodyParams(schema, false) expect(params).toHaveLength(1) expect(params[0].type).toBe('object') - // Confirm "null" is NOT in the type expect(params[0].type).not.toContain('null') }) - // ── Existing OAS 3.0 nullable path still works ─────────────────────────── it('still handles OAS 3.0 nullable: true', async () => { const schema = { type: 'object', @@ -186,7 +174,6 @@ describe('getBodyParams — OAS 3.1 nullable handling', () => { expect(params[0].type).toBe('string or null') }) - // ── Normal non-nullable object body params ─────────────────────────────── it('renders a plain string param without null', async () => { const schema = { type: 'object', From de72be68087bb9cbc3efd8434d045019869cd2f1 Mon Sep 17 00:00:00 2001 From: "Hector A." Date: Thu, 1 Oct 2026 16:32:58 +0000 Subject: [PATCH 07/26] Migrate SecretScanningTable filters to Primer Brand (#63624) --- .../tests/playwright-secret-scanning.spec.ts | 69 +++++++++++++++++++ .../SecretScanningTable.module.scss | 3 + .../components/SecretScanningTable.tsx | 40 +++++++---- 3 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 src/secret-scanning/components/SecretScanningTable.module.scss diff --git a/src/fixtures/tests/playwright-secret-scanning.spec.ts b/src/fixtures/tests/playwright-secret-scanning.spec.ts index 60df4fa6096c..cf8d459ba466 100644 --- a/src/fixtures/tests/playwright-secret-scanning.spec.ts +++ b/src/fixtures/tests/playwright-secret-scanning.spec.ts @@ -3,6 +3,75 @@ import { test, expect } from '@playwright/test' const PAGE_PATH = '/code-security/reference/secret-security/supported-secret-scanning-patterns' test.describe('Secret scanning DataTable accessibility', () => { + for (const key of ['Enter', 'Space']) { + test(`filter menu selects once with ${key}`, async ({ page }) => { + await page.goto(PAGE_PATH) + + const trigger = page.getByRole('button', { name: /^Push protection:/ }) + await trigger.focus() + await page.keyboard.press('Enter') + + const menu = page.getByRole('menu', { name: 'Push protection', exact: true }) + await expect(menu.getByRole('menuitemradio', { name: 'All', exact: true })).toBeFocused() + const yesOption = menu.getByRole('menuitemradio', { name: 'Yes', exact: true }) + await expect(yesOption).toHaveAttribute('aria-checked', 'false') + + await menu.evaluate((element) => { + element.addEventListener('keydown', (event) => { + if (event instanceof KeyboardEvent && (event.key === 'Enter' || event.key === ' ')) { + document.body.dataset.filterKeydowns = String( + Number(document.body.dataset.filterKeydowns || 0) + 1, + ) + } + }) + element.addEventListener('click', () => { + document.body.dataset.filterClicks = String( + Number(document.body.dataset.filterClicks || 0) + 1, + ) + }) + }) + + await yesOption.focus() + await page.keyboard.press(key) + + await expect(menu).toHaveCount(0) + await expect(trigger).toHaveText('Push protection: Yes') + await expect(trigger).toBeFocused() + await expect(page.locator('body')).toHaveAttribute('data-filter-clicks', '1') + await expect(page.locator('body')).not.toHaveAttribute('data-filter-keydowns') + + await trigger.click() + await expect(menu.getByRole('menuitemradio', { checked: true })).toHaveText('Yes') + await page.keyboard.press('Escape') + }) + } + + test('filter menus are at least as wide as their triggers', async ({ page }) => { + await page.goto(PAGE_PATH) + + for (const label of [ + 'Push protection', + 'Validity check', + 'Partner alert', + 'Metadata check', + 'Base64', + ]) { + const trigger = page.getByRole('button', { name: new RegExp(`^${label}:`) }) + await trigger.click() + + const menu = page.getByRole('menu', { name: label }) + await expect(menu).toBeVisible() + + const triggerWidth = await trigger.evaluate( + (element) => element.getBoundingClientRect().width, + ) + const menuWidth = await menu.evaluate((element) => element.getBoundingClientRect().width) + expect(menuWidth + 1).toBeGreaterThanOrEqual(triggerWidth) + + await page.keyboard.press('Escape') + } + }) + test('table has an accessible name via aria-labelledby', async ({ page }) => { await page.goto(PAGE_PATH) diff --git a/src/secret-scanning/components/SecretScanningTable.module.scss b/src/secret-scanning/components/SecretScanningTable.module.scss new file mode 100644 index 000000000000..d8fd63d197b7 --- /dev/null +++ b/src/secret-scanning/components/SecretScanningTable.module.scss @@ -0,0 +1,3 @@ +.filterDropdown [role="menu"] { + min-width: 100%; +} diff --git a/src/secret-scanning/components/SecretScanningTable.tsx b/src/secret-scanning/components/SecretScanningTable.tsx index b1d0fbf0295d..6d32e0c8e53e 100644 --- a/src/secret-scanning/components/SecretScanningTable.tsx +++ b/src/secret-scanning/components/SecretScanningTable.tsx @@ -1,13 +1,14 @@ import React, { useState, useMemo, useEffect, useRef, useCallback } from 'react' import { DataTable, Table } from '@primer/react/experimental' -import { TextInput, ActionMenu, ActionList } from '@primer/react' -import { Pagination, Button } from '@primer/react-brand' +import { TextInput, ActionMenu, Pagination, Button } from '@primer/react-brand' import { debounce } from 'lodash-es' import { useTranslation } from '@/languages/components/useTranslation' import { sendEvent } from '@/events/components/events' import { EventType } from '@/events/types' import { sanitizeSearchQuery } from '@/search/lib/sanitize-search-query' +import { onActionMenuItemKeyDownCapture } from '@/frame/components/lib/action-menu' import type { SecretScanningData } from '@/types' +import styles from './SecretScanningTable.module.scss' const PAGE_SIZE = 25 @@ -203,6 +204,7 @@ export function SecretScanningTable({ data }: { data: SecretScanningData[] }) { )} void }) { const { t } = useTranslation('secret_scanning') + const selectedLabel = + value === 'all' ? t('filter_all') : value === 'yes' ? t('filter_yes') : t('filter_no') return ( - - - {label}:{' '} - {value === 'all' ? t('filter_all') : value === 'yes' ? t('filter_yes') : t('filter_no')} - - - +
    + onChange(selectedValue as 'all' | 'yes' | 'no')} + > + + {label}: {selectedLabel} + + {(['all', 'yes', 'no'] as const).map((opt) => ( - onChange(opt)}> + {opt === 'all' ? t('filter_all') : opt === 'yes' ? t('filter_yes') : t('filter_no')} - + ))} - - - + + +
    ) } From f572b79089f2de09cfbb7335a993de1fca0c485c Mon Sep 17 00:00:00 2001 From: docs-bot <77750099+docs-bot@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:42:31 +0000 Subject: [PATCH 08/26] GraphQL schema update (#63640) Co-authored-by: github-merge-queue <118344674+github-merge-queue@users.noreply.github.com> --- src/graphql/data/fpt/changelog.json | 13 +++++++++++++ src/graphql/data/fpt/schema-projects.json | 7 +++++++ src/graphql/data/fpt/schema.docs.graphql | 5 +++++ src/graphql/data/ghec/schema-projects.json | 7 +++++++ src/graphql/data/ghec/schema.docs.graphql | 5 +++++ 5 files changed, 37 insertions(+) diff --git a/src/graphql/data/fpt/changelog.json b/src/graphql/data/fpt/changelog.json index 79dd889433df..c89226c41fc7 100644 --- a/src/graphql/data/fpt/changelog.json +++ b/src/graphql/data/fpt/changelog.json @@ -1,4 +1,17 @@ [ + { + "schemaChanges": [ + { + "title": "The GraphQL schema includes these changes:", + "changes": [ + "

    Input field id of type String was added to input object type 'ProjectV2Iteration'

    " + ] + } + ], + "previewChanges": [], + "upcomingChanges": [], + "date": "2026-10-01" + }, { "schemaChanges": [ { diff --git a/src/graphql/data/fpt/schema-projects.json b/src/graphql/data/fpt/schema-projects.json index 2bffcb251b28..be0a4337d73a 100644 --- a/src/graphql/data/fpt/schema-projects.json +++ b/src/graphql/data/fpt/schema-projects.json @@ -6996,6 +6996,13 @@ "id": "int", "href": "/graphql/reference/other#scalar-int" }, + { + "name": "id", + "description": "

    The ID of an existing iteration. Include this to preserve the iteration's identity during replacement updates.

    ", + "type": "String", + "id": "string", + "href": "/graphql/reference/other#scalar-string" + }, { "name": "startDate", "description": "

    The start date for the iteration.

    ", diff --git a/src/graphql/data/fpt/schema.docs.graphql b/src/graphql/data/fpt/schema.docs.graphql index 94fb6074bb62..508138ae0dd6 100644 --- a/src/graphql/data/fpt/schema.docs.graphql +++ b/src/graphql/data/fpt/schema.docs.graphql @@ -42757,6 +42757,11 @@ input ProjectV2Iteration @docsCategory(name: "projects") { """ duration: Int! + """ + The ID of an existing iteration. Include this to preserve the iteration's identity during replacement updates. + """ + id: String + """ The start date for the iteration. """ diff --git a/src/graphql/data/ghec/schema-projects.json b/src/graphql/data/ghec/schema-projects.json index 2bffcb251b28..be0a4337d73a 100644 --- a/src/graphql/data/ghec/schema-projects.json +++ b/src/graphql/data/ghec/schema-projects.json @@ -6996,6 +6996,13 @@ "id": "int", "href": "/graphql/reference/other#scalar-int" }, + { + "name": "id", + "description": "

    The ID of an existing iteration. Include this to preserve the iteration's identity during replacement updates.

    ", + "type": "String", + "id": "string", + "href": "/graphql/reference/other#scalar-string" + }, { "name": "startDate", "description": "

    The start date for the iteration.

    ", diff --git a/src/graphql/data/ghec/schema.docs.graphql b/src/graphql/data/ghec/schema.docs.graphql index 94fb6074bb62..508138ae0dd6 100644 --- a/src/graphql/data/ghec/schema.docs.graphql +++ b/src/graphql/data/ghec/schema.docs.graphql @@ -42757,6 +42757,11 @@ input ProjectV2Iteration @docsCategory(name: "projects") { """ duration: Int! + """ + The ID of an existing iteration. Include this to preserve the iteration's identity during replacement updates. + """ + id: String + """ The start date for the iteration. """ From 19203d5c2c6d8da11df1c48817aa418b638bc1bb Mon Sep 17 00:00:00 2001 From: Patrick Le Quere <48521719+plequere-ms@users.noreply.github.com> Date: Thu, 1 Oct 2026 16:51:16 +0000 Subject: [PATCH 09/26] Document 12-hour maximum codespace lifetime (#63572) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- content/codespaces/about-codespaces/deep-dive.md | 3 +-- .../understanding-the-codespace-lifecycle.md | 10 ++++++++++ .../stopping-and-starting-a-codespace.md | 7 +++---- ...etting-your-timeout-period-for-github-codespaces.md | 2 ++ 4 files changed, 16 insertions(+), 6 deletions(-) diff --git a/content/codespaces/about-codespaces/deep-dive.md b/content/codespaces/about-codespaces/deep-dive.md index feeeefb1cd0e..b714a48b6113 100644 --- a/content/codespaces/about-codespaces/deep-dive.md +++ b/content/codespaces/about-codespaces/deep-dive.md @@ -82,7 +82,7 @@ If you work on codespaces in {% data variables.product.prodname_vscode %}, you c ### Closing or stopping your codespace -Your codespace will keep running while you are using it, but will time out after a period of inactivity. File changes from the editor and terminal output are counted as activity, so your codespace will not time out if terminal output is continuing. The default inactivity timeout period is 30 minutes. You can define your personal timeout setting for codespaces you create, but this may be overruled by an organization timeout policy. For more information, see [AUTOTITLE](/codespaces/setting-your-user-preferences/setting-your-timeout-period-for-github-codespaces). +Your codespace will keep running while you are using it, up to a maximum lifetime of 12 hours, but will time out after a period of inactivity. File changes from the editor and terminal output are counted as activity, so your codespace will not time out if terminal output is continuing. The default inactivity timeout period is 30 minutes. You can define your personal timeout setting for codespaces you create, but this may be overruled by an organization timeout policy. For more information, see [AUTOTITLE](/codespaces/setting-your-user-preferences/setting-your-timeout-period-for-github-codespaces). For more information about the maximum lifetime, see [AUTOTITLE](/codespaces/about-codespaces/understanding-the-codespace-lifecycle#maximum-lifetime-of-a-codespace). If a codespace times out it will stop running, but you can restart it from the browser tab (if you were using the codespace in the browser), from within {% data variables.product.prodname_vscode_shortname %}, or from your list of codespaces at [https://github.com/codespaces](https://github.com/codespaces). @@ -152,4 +152,3 @@ If you want to make changes to your codespace that will be more robust over rebu * [AUTOTITLE](/codespaces/managing-codespaces-for-your-organization/enabling-or-disabling-github-codespaces-for-your-organization) * [AUTOTITLE](/codespaces/managing-codespaces-for-your-organization/managing-the-cost-of-github-codespaces-in-your-organization) * [AUTOTITLE](/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration) -* [AUTOTITLE](/codespaces/about-codespaces/understanding-the-codespace-lifecycle) diff --git a/content/codespaces/about-codespaces/understanding-the-codespace-lifecycle.md b/content/codespaces/about-codespaces/understanding-the-codespace-lifecycle.md index cf0aca59023b..015ae64d4502 100644 --- a/content/codespaces/about-codespaces/understanding-the-codespace-lifecycle.md +++ b/content/codespaces/about-codespaces/understanding-the-codespace-lifecycle.md @@ -44,6 +44,16 @@ If you leave your codespace running without interaction, or if you exit your cod When a codespace times out, your data is preserved from the last time your changes were saved. For more information, see [Saving changes in a codespace](#saving-changes-in-a-codespace). +## Maximum lifetime of a codespace + +A codespace has a maximum lifetime of 12 hours, regardless of your idle timeout policy or settings. This limit applies even while you are actively using the codespace. + +As a codespace approaches this 12-hour limit, you will see the following warning: + +> Your codespace must be stopped soon. Stop and then reconnect to your codespace to keep working. + +Your data is saved, then the codespace automatically stops. To continue working, restart the codespace. For more information, see [AUTOTITLE](/codespaces/developing-in-a-codespace/stopping-and-starting-a-codespace#restarting-a-codespace). + ## Rebuilding a codespace You can rebuild your codespace to implement changes you've made to your dev container configuration. For most uses, you can create a new codespace as an alternative to rebuilding a codespace. By default, when you rebuild your codespace, {% data variables.product.prodname_github_codespaces %} will reuse images from your cache to speed up the rebuild process. Alternatively, you can perform a full rebuild, which clears your cache and rebuilds the container with fresh images. diff --git a/content/codespaces/developing-in-a-codespace/stopping-and-starting-a-codespace.md b/content/codespaces/developing-in-a-codespace/stopping-and-starting-a-codespace.md index 3fdac1a8134c..d793581a774a 100644 --- a/content/codespaces/developing-in-a-codespace/stopping-and-starting-a-codespace.md +++ b/content/codespaces/developing-in-a-codespace/stopping-and-starting-a-codespace.md @@ -16,6 +16,9 @@ category: {% data reusables.codespaces.stopping-a-codespace %} +> [!NOTE] +> A codespace also has a maximum lifetime of 12 hours, regardless of its idle timeout setting. For more information, see [AUTOTITLE](/codespaces/about-codespaces/understanding-the-codespace-lifecycle#maximum-lifetime-of-a-codespace). + Regardless of where you created or access your codespaces, you can view and manage them in your browser at https://github.com/codespaces. ## Stopping a codespace @@ -86,7 +89,3 @@ When you restart a codespace you can choose to open it in {% data variables.prod 1. In the list of codespaces, select the codespace you want to restart. {% endvscode %} - -## Further reading - -* [AUTOTITLE](/codespaces/about-codespaces/understanding-the-codespace-lifecycle) diff --git a/content/codespaces/setting-your-user-preferences/setting-your-timeout-period-for-github-codespaces.md b/content/codespaces/setting-your-user-preferences/setting-your-timeout-period-for-github-codespaces.md index c240ac25a588..bd6b036dfbb6 100644 --- a/content/codespaces/setting-your-user-preferences/setting-your-timeout-period-for-github-codespaces.md +++ b/content/codespaces/setting-your-user-preferences/setting-your-timeout-period-for-github-codespaces.md @@ -17,6 +17,8 @@ category: A codespace will stop running after a period of inactivity. By default this period is 30 minutes, but you can specify a longer or shorter default timeout period in your personal settings on {% data variables.product.prodname_dotcom %}. The updated setting will apply to any new codespaces you create. You can also specify a timeout when you use {% data variables.product.prodname_cli %} to create a codespace. +Regardless of your idle timeout setting, a codespace has a maximum lifetime of 12 hours. For more information, see [AUTOTITLE](/codespaces/about-codespaces/understanding-the-codespace-lifecycle#maximum-lifetime-of-a-codespace). + > [!WARNING] > Codespaces compute usage is billed for the duration for which a codespace is active. If you're not using a codespace but it remains running, and hasn't yet timed out, you are billed for the total time that the codespace was active, irrespective of whether you were using it. For more information, see [AUTOTITLE](/billing/concepts/product-billing/github-codespaces#pricing). From f0f5a21ea0afe6d18c30c4e5b3ffc43365ee56c1 Mon Sep 17 00:00:00 2001 From: Laura Coursen Date: Thu, 1 Oct 2026 16:55:41 +0000 Subject: [PATCH 10/26] Tag the enterprise onboarding journey with docsTeamMetrics after docs dedup (#63139) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../admin/concepts/enterprise-best-practices/organize-work.md | 2 ++ .../admin/concepts/enterprise-best-practices/use-innersource.md | 2 ++ .../enterprise-fundamentals/automations-in-your-enterprise.md | 2 ++ .../concepts/enterprise-fundamentals/roles-in-an-enterprise.md | 2 ++ .../concepts/enterprise-fundamentals/teams-in-an-enterprise.md | 2 ++ .../security-and-compliance/audit-log-for-an-enterprise.md | 2 ++ .../concepts/security-and-compliance/enterprise-policies.md | 2 ++ .../adding-organizations-to-your-enterprise.md | 2 ++ .../governing-how-people-use-repositories-in-your-enterprise.md | 2 ++ ...ing-custom-properties-for-repositories-in-your-enterprise.md | 2 ++ .../managing-roles-in-your-enterprise/assign-roles.md | 2 ++ .../managing-roles-in-your-enterprise/create-custom-roles.md | 2 ++ .../identify-role-requirements.md | 2 ++ .../create-enterprise-teams.md | 2 ++ .../creating-github-apps-for-your-enterprise.md | 2 ++ .../installing-a-github-app-on-your-enterprise.md | 2 ++ 16 files changed, 32 insertions(+) diff --git a/content/admin/concepts/enterprise-best-practices/organize-work.md b/content/admin/concepts/enterprise-best-practices/organize-work.md index c7625310d5cf..0b30cf98c7f4 100644 --- a/content/admin/concepts/enterprise-best-practices/organize-work.md +++ b/content/admin/concepts/enterprise-best-practices/organize-work.md @@ -17,6 +17,8 @@ redirect_from: allowTitleToDifferFromFilename: true category: - Get started with GitHub Enterprise +docsTeamMetrics: + - enterprise-onboarding --- ## Use organizations for work or governance diff --git a/content/admin/concepts/enterprise-best-practices/use-innersource.md b/content/admin/concepts/enterprise-best-practices/use-innersource.md index 0592f8d190fb..736934c1b4f8 100644 --- a/content/admin/concepts/enterprise-best-practices/use-innersource.md +++ b/content/admin/concepts/enterprise-best-practices/use-innersource.md @@ -11,6 +11,8 @@ redirect_from: allowTitleToDifferFromFilename: true category: - Get started with GitHub Enterprise +docsTeamMetrics: + - enterprise-onboarding --- You can use innersource practices to drive collaboration and productivity in your enterprise. Innersource makes it easy for all employees to discover and reuse work. This allows development teams to learn from each other's work, share their expertise, and avoid duplicating effort to recreate common services. diff --git a/content/admin/concepts/enterprise-fundamentals/automations-in-your-enterprise.md b/content/admin/concepts/enterprise-fundamentals/automations-in-your-enterprise.md index f62d3c83a040..9747fbc30d1c 100644 --- a/content/admin/concepts/enterprise-fundamentals/automations-in-your-enterprise.md +++ b/content/admin/concepts/enterprise-fundamentals/automations-in-your-enterprise.md @@ -9,6 +9,8 @@ redirect_from: - /enterprise-onboarding/github-apps/automations-in-your-enterprise category: - Get started with GitHub Enterprise +docsTeamMetrics: + - enterprise-onboarding --- Automation on {% data variables.product.github %} typically involves multiple components working together. The most important {% data variables.product.github %} native components are: diff --git a/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md b/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md index daea04b8d6bc..9dcee8dec56b 100644 --- a/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md +++ b/content/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise.md @@ -11,6 +11,8 @@ redirect_from: contentType: concepts category: - Get started with GitHub Enterprise +docsTeamMetrics: + - enterprise-onboarding --- ## What are roles? diff --git a/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md b/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md index fd5029dc296c..573f0fea7e2a 100644 --- a/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md +++ b/content/admin/concepts/enterprise-fundamentals/teams-in-an-enterprise.md @@ -11,6 +11,8 @@ redirect_from: contentType: concepts category: - Get started with GitHub Enterprise +docsTeamMetrics: + - enterprise-onboarding --- ## What are teams? diff --git a/content/admin/concepts/security-and-compliance/audit-log-for-an-enterprise.md b/content/admin/concepts/security-and-compliance/audit-log-for-an-enterprise.md index feae403c0956..6fff72d8760a 100644 --- a/content/admin/concepts/security-and-compliance/audit-log-for-an-enterprise.md +++ b/content/admin/concepts/security-and-compliance/audit-log-for-an-enterprise.md @@ -20,6 +20,8 @@ versions: contentType: concepts category: - Secure and govern your enterprise +docsTeamMetrics: + - enterprise-onboarding --- ## What are audit logs? diff --git a/content/admin/concepts/security-and-compliance/enterprise-policies.md b/content/admin/concepts/security-and-compliance/enterprise-policies.md index a0ae3313a179..e49e3021b117 100644 --- a/content/admin/concepts/security-and-compliance/enterprise-policies.md +++ b/content/admin/concepts/security-and-compliance/enterprise-policies.md @@ -12,6 +12,8 @@ redirect_from: - /enterprise-onboarding/govern-people-and-repositories/about-enterprise-policies category: - Secure and govern your enterprise +docsTeamMetrics: + - enterprise-onboarding --- ## What are enterprise policies and why are they important? diff --git a/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/adding-organizations-to-your-enterprise.md b/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/adding-organizations-to-your-enterprise.md index a1799d3f5e6a..80a0936f2b1f 100644 --- a/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/adding-organizations-to-your-enterprise.md +++ b/content/admin/managing-accounts-and-repositories/managing-organizations-in-your-enterprise/adding-organizations-to-your-enterprise.md @@ -16,6 +16,8 @@ permissions: Enterprise owners contentType: how-tos category: - Manage accounts and repositories +docsTeamMetrics: + - enterprise-onboarding --- There are three ways to add organizations to your enterprise. diff --git a/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/governing-how-people-use-repositories-in-your-enterprise.md b/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/governing-how-people-use-repositories-in-your-enterprise.md index 7a032ed68348..6bde518a2d44 100644 --- a/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/governing-how-people-use-repositories-in-your-enterprise.md +++ b/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/governing-how-people-use-repositories-in-your-enterprise.md @@ -10,6 +10,8 @@ category: - Manage accounts and repositories redirect_from: - /enterprise-onboarding/govern-people-and-repositories/create-repository-policies +docsTeamMetrics: + - enterprise-onboarding --- {% data reusables.enterprise.repo-policy-rules-preview %} diff --git a/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/managing-custom-properties-for-repositories-in-your-enterprise.md b/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/managing-custom-properties-for-repositories-in-your-enterprise.md index 5d58c3ea2459..327dfd5dce42 100644 --- a/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/managing-custom-properties-for-repositories-in-your-enterprise.md +++ b/content/admin/managing-accounts-and-repositories/managing-repositories-in-your-enterprise/managing-custom-properties-for-repositories-in-your-enterprise.md @@ -9,6 +9,8 @@ versions: shortTitle: Custom properties category: - Manage accounts and repositories +docsTeamMetrics: + - enterprise-onboarding --- Custom properties allow you to decorate your repositories with information such as compliance frameworks, data sensitivity, or project details. Custom properties are private and can only be viewed by people with read permissions to the repository. An enterprise can have up to 100 property definitions. An allowed value list can have up to 200 items. diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles.md index 0c1007fbbd22..c080b577f541 100644 --- a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles.md +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/assign-roles.md @@ -10,6 +10,8 @@ redirect_from: contentType: how-tos category: - Manage accounts and repositories +docsTeamMetrics: + - enterprise-onboarding --- Enterprise owners can assign custom and predefined **enterprise roles** to users and teams. Some roles can be assigned to enterprise teams, whereas other roles are only available for individual users. Find the section below for the role you want to assign. diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles.md index 4621d3d6bfe7..06994e7b64b1 100644 --- a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles.md +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/create-custom-roles.md @@ -10,6 +10,8 @@ redirect_from: contentType: how-tos category: - Manage accounts and repositories +docsTeamMetrics: + - enterprise-onboarding --- To tailor access management to your company's needs, you can create custom roles for your{% ifversion enterprise-custom-roles %} enterprise account and{% endif %} organizations. diff --git a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements.md b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements.md index a420b8cc431e..45a200f08d23 100644 --- a/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements.md +++ b/content/admin/managing-accounts-and-repositories/managing-roles-in-your-enterprise/identify-role-requirements.md @@ -10,6 +10,8 @@ redirect_from: - /enterprise-onboarding/setting-up-organizations-and-teams/identify-role-requirements category: - Manage accounts and repositories +docsTeamMetrics: + - enterprise-onboarding --- Roles control people's access to settings and resources in your enterprise and organizations. For an introduction to roles, see [AUTOTITLE](/admin/concepts/enterprise-fundamentals/roles-in-an-enterprise). diff --git a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md index 78808134811c..7d0c728f6001 100644 --- a/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md +++ b/content/admin/managing-accounts-and-repositories/managing-users-in-your-enterprise/create-enterprise-teams.md @@ -12,6 +12,8 @@ redirect_from: contentType: how-tos category: - Manage accounts and repositories +docsTeamMetrics: + - enterprise-onboarding --- To simplify administration at scale, you can create enterprise teams. {% data reusables.enterprise.enterprise-teams-can %} diff --git a/content/admin/managing-github-apps-for-your-enterprise/creating-github-apps-for-your-enterprise.md b/content/admin/managing-github-apps-for-your-enterprise/creating-github-apps-for-your-enterprise.md index 94a66e9e5fba..22e92af1f47c 100644 --- a/content/admin/managing-github-apps-for-your-enterprise/creating-github-apps-for-your-enterprise.md +++ b/content/admin/managing-github-apps-for-your-enterprise/creating-github-apps-for-your-enterprise.md @@ -11,6 +11,8 @@ redirect_from: contentType: how-tos category: - Enable GitHub features for your enterprise +docsTeamMetrics: + - enterprise-onboarding --- You can create a {% data variables.product.prodname_github_app %} under your enterprise account. The app can only be installed on{% ifversion enterprise-installed-apps %} your enterprise or{% endif %} organizations within your enterprise, and can only be authorized by members of your enterprise. The app can't be installed on user accounts. diff --git a/content/apps/using-github-apps/installing-a-github-app-on-your-enterprise.md b/content/apps/using-github-apps/installing-a-github-app-on-your-enterprise.md index 696084ab2d33..4e39cfaf76b6 100644 --- a/content/apps/using-github-apps/installing-a-github-app-on-your-enterprise.md +++ b/content/apps/using-github-apps/installing-a-github-app-on-your-enterprise.md @@ -9,6 +9,8 @@ redirect_from: permissions: 'Enterprise owners can install {% data variables.product.prodname_github_apps %} on their enterprise. App managers cannot install apps at the enterprise level.' category: - Install and authorize apps +docsTeamMetrics: + - enterprise-onboarding --- > [!NOTE] From db4f78cf90c170dd304ac9bc9f0a1c481b9f5be6 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 1 Oct 2026 17:02:33 +0000 Subject: [PATCH 11/26] Move CodeQL script setup instructions into the README (#63234) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 17588eeb-788a-4f82-9d36-b5079fb36521 --- src/codeql-queries/README.md | 40 +++++++++++++++++-- .../generate-code-quality-query-list.ts | 31 +++++++------- .../generate-code-scanning-query-list.ts | 30 +++++++------- 3 files changed, 65 insertions(+), 36 deletions(-) diff --git a/src/codeql-queries/README.md b/src/codeql-queries/README.md index c7297459ecce..72bc43777c3d 100644 --- a/src/codeql-queries/README.md +++ b/src/codeql-queries/README.md @@ -64,9 +64,43 @@ The workflow automatically creates a new pull request with changes from both scr ## Local development -To run the pipeline locally, see the comments in the scripts: -- Security queries: [generate-code-scanning-query-list.ts](scripts/generate-code-scanning-query-list.ts) -- Code quality queries: [generate-code-quality-query-list.ts](scripts/generate-code-quality-query-list.ts) +Both scripts need the CodeQL CLI and a local clone of `github/codeql`. The security script also needs a private npm package. + +### 1. Clone github/codeql + +The scripts resolve query suites by path, so the repo has to be on disk. + +```sh +git clone git@github.com:github/codeql.git /tmp/codeql +``` + +### 2. Install the CodeQL CLI + +```sh +gh extension install github/gh-codeql +gh codeql set-channel nightly +gh codeql version +``` + +`gh codeql version` prints where it installed the executable, something like `~/.local/share/gh/extensions/gh-codeql/dist/nightly/codeql-bundle-/codeql`. Pass that path as `--codeql-path`. + +### 3. Install @github/cocofix + +Only `generate-code-scanning-query-list.ts` needs this, for autofix support data. It's a private package, so get the `DOCS_BOT_PAT_BASE` PAT from the vault, export it, then run this from the root of the repo: + +```sh +npm i --no-save '--@github:registry=https://npm.pkg.github.com' '--//npm.pkg.github.com/:_authToken=${DOCS_BOT_PAT_BASE}' @github/cocofix +``` + +### 4. Run a script + +```sh +npm run generate-code-quality-query-list -- \ + --codeql-path \ + --codeql-dir /tmp/codeql python | tee /tmp/python.md +``` + +Use `generate-code-scanning-query-list` for the security tables. The last argument is the language. ## Content team diff --git a/src/codeql-queries/scripts/generate-code-quality-query-list.ts b/src/codeql-queries/scripts/generate-code-quality-query-list.ts index eb422526288c..a8dcfcf66c92 100644 --- a/src/codeql-queries/scripts/generate-code-quality-query-list.ts +++ b/src/codeql-queries/scripts/generate-code-quality-query-list.ts @@ -1,13 +1,10 @@ -// Generates reusable Markdown listing code quality queries for one language, with categories. -// Requires a local github/codeql clone and a CodeQL CLI executable. -// Set up the clone with git clone git@github.com:github/codeql.git /tmp/codeql. -// Install the CLI with gh extension install github/gh-codeql, then gh codeql set-channel nightly. -// Run gh codeql version to find the installed codeql path. -// Example: -// npm run generate-code-quality-query-list -- \ -// --codeql-path ~/.local/share/gh/extensions/gh-codeql/dist/nightly/codeql-bundle-*/codeql \ -// --codeql-dir /tmp/codeql python | tee /tmp/python.md -// Inspect the generated Markdown with less /tmp/python.md. +/** + * Generates a Markdown table of the code quality queries for one language, + * with their categories, to be saved as a reusable. + * + * Running this locally needs the CodeQL CLI and a clone of github/codeql. + * See "Local development" in src/codeql-queries/README.md. + */ import fs from 'fs' import { execFileSync } from 'child_process' @@ -94,7 +91,6 @@ async function main(options: Options, language: string) { const categories = getCategories(tags || '') const url = getDocsLink(language, id) - // Category-less queries have no code quality docs row. if (categories.length) { queries[id] = { url, name, categories, severity: severity || 'N/A' } } else { @@ -107,7 +103,6 @@ async function main(options: Options, language: string) { } function decorate(query: Query): QueryExtended { - // Maintainability outranks reliability for table sorting. const primaryCategory = query.categories.includes('maintainability') ? 'maintainability' : query.categories.includes('reliability') @@ -122,7 +117,7 @@ async function main(options: Options, language: string) { const entries = Object.values(queries).map(decorate) - // Sort by primary category, then alphabetically by name. + // Maintainability first, then alphabetical by name. entries.sort((a, b) => { if (a.primaryCategory === 'maintainability' && b.primaryCategory !== 'maintainability') return -1 @@ -172,7 +167,8 @@ function getMetadata(options: Options, queryFile: string): QueryMetadata { }) const parsed = JSON.parse(metadataJson) - // CodeQL emits severity through several metadata shapes, depending on the query source. + // `codeql resolve metadata` reports @problem.severity in several different JSON shapes, + // so try each one. const severity = parsed.problem?.severity || // Nested: { problem: { severity: "error" } } parsed['@problem']?.severity || // Nested with @: { "@problem": { severity: "error" } } @@ -182,7 +178,7 @@ function getMetadata(options: Options, queryFile: string): QueryMetadata { parsed['@severity'] // With @: { "@severity": "error" } if (options.verbose) { - // Verbose mode logs metadata keys once to avoid noisy output. + // Only dump the key list once. if (!getMetadata.shownKeys) { console.log(chalk.yellow('Available metadata keys:'), Object.keys(parsed)) if (parsed.problem) { @@ -209,13 +205,14 @@ function getMetadata(options: Options, queryFile: string): QueryMetadata { getMetadata.shownKeys = false -// Example: cpp and external-entity-expansion become +// getDocsLink('cpp', 'external-entity-expansion') returns // https://codeql.github.com/codeql-query-help/cpp/cpp-external-entity-expansion/ function getDocsLink(language: string, queryId: string) { return `https://codeql.github.com/codeql-query-help/${language}/${queryId.replaceAll('/', '-')}/` } -// Example tags with maintainability and reliability return those categories in source order. +// getCategories('maintainability readability reliability external/cwe/cwe-1078') +// returns ['maintainability', 'reliability'] function getCategories(tags: string) { const categories: string[] = [] for (const tag of tags.split(/\s+/g)) { diff --git a/src/codeql-queries/scripts/generate-code-scanning-query-list.ts b/src/codeql-queries/scripts/generate-code-scanning-query-list.ts index e9bafc2b98dd..a8d372b520da 100644 --- a/src/codeql-queries/scripts/generate-code-scanning-query-list.ts +++ b/src/codeql-queries/scripts/generate-code-scanning-query-list.ts @@ -1,16 +1,11 @@ -// Generates reusable Markdown listing CodeQL code scanning queries for one language, with CWEs. -// Requires a local github/codeql clone and a CodeQL CLI executable. -// Set up the clone with git clone git@github.com:github/codeql.git /tmp/codeql. -// Install the CLI with gh extension install github/gh-codeql, then gh codeql set-channel nightly. -// Run gh codeql version to find the installed codeql path. -// Also requires @github/cocofix, installed locally with DOCS_BOT_PAT_BASE from the vault: -// npm i --no-save '--@github:registry=https://npm.pkg.github.com' \ -// '--//npm.pkg.github.com/:_authToken=${DOCS_BOT_PAT_BASE}' @github/cocofix -// Example: -// npm run generate-code-scanning-query-list -- \ -// --codeql-path ~/.local/share/gh/extensions/gh-codeql/dist/nightly/codeql-bundle-*/codeql \ -// --codeql-dir /tmp/codeql python | tee /tmp/python.md -// Inspect the generated Markdown with less /tmp/python.md. +/** + * Generates a Markdown table of the security queries for one language, with + * their CWEs, to be saved as a reusable. + * + * Running this locally needs the CodeQL CLI, a clone of github/codeql, + * and the private @github/cocofix package. + * See "Local development" in src/codeql-queries/README.md. + */ import fs from 'fs' import { execFileSync } from 'child_process' @@ -114,7 +109,9 @@ async function main(options: Options, language: string) { const url = getDocsLink(language, id) const autofixSupport = autofixSupportedQueryIds.includes(id) ? 'default' : 'none' - // CWE-less queries cover metadata or metrics and have no docs link. + // Queries without CWEs are metadata and metrics queries, + // like counting lines of code. + // They have no docs link, so skip them. if (cwes.length) { if (!(id in queries)) { queries[id] = { url, name, packs: [], cwes, autofixSupport } @@ -199,13 +196,14 @@ function getMetadata(options: Options, queryFile: string): QueryMetadata { return parsed } -// Example: cpp and external-entity-expansion become +// getDocsLink('cpp', 'external-entity-expansion') returns // https://codeql.github.com/codeql-query-help/cpp/cpp-external-entity-expansion/ function getDocsLink(language: string, queryId: string) { return `https://codeql.github.com/codeql-query-help/${language}/${queryId.replaceAll('/', '-')}/` } -// Example tags with external/cwe/cwe-1078 and external/cwe/cwe-670 return 1078 and 670. +// getCWEs('maintainability external/cwe/cwe-1078 external/cwe/cwe-670') +// returns ['1078', '670'] function getCWEs(tags: string) { const cwes: string[] = [] for (const tag of tags.split(/\s+/g)) { From e0b96b9cf1672db2f026fe2021f388d086bf84d9 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 1 Oct 2026 17:02:46 +0000 Subject: [PATCH 12/26] Fix intro frontmatter for generated CodeQL CLI manual pages (#63240) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 17588eeb-788a-4f82-9d36-b5079fb36521 --- .../codeql/codeql-cli-manual/resolve-library-paths.md | 3 +++ src/codeql-cli/scripts/convert-markdown-for-docs.ts | 4 +++- src/codeql-cli/tests/convert-markdown-for-docs.ts | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/content/code-security/reference/code-scanning/codeql/codeql-cli-manual/resolve-library-paths.md b/content/code-security/reference/code-scanning/codeql/codeql-cli-manual/resolve-library-paths.md index 48a015357a33..5324fd264652 100644 --- a/content/code-security/reference/code-scanning/codeql/codeql-cli-manual/resolve-library-paths.md +++ b/content/code-security/reference/code-scanning/codeql/codeql-cli-manual/resolve-library-paths.md @@ -9,6 +9,9 @@ product: '{% data reusables.gated-features.codeql %}' category: - Find CodeQL CLI commands autogenerated: codeql-cli +intro: |- + [Deep plumbing] Determine QL library paths and dbschemes for multiple + queries. --- diff --git a/src/codeql-cli/scripts/convert-markdown-for-docs.ts b/src/codeql-cli/scripts/convert-markdown-for-docs.ts index 7bdf8bcd34bb..97d13dd4b964 100644 --- a/src/codeql-cli/scripts/convert-markdown-for-docs.ts +++ b/src/codeql-cli/scripts/convert-markdown-for-docs.ts @@ -92,7 +92,9 @@ export async function convertContentToDocs( let currentNodeIsDescription = false visit(ast, (rawNode) => { const node = rawNode as unknown as MdNode - if (node.type !== 'heading' && node.type !== 'paragraph') return false + // A bare return is CONTINUE. Returning false would mean EXIT, + // which stops the whole walk on the root node. + if (node.type !== 'heading' && node.type !== 'paragraph') return // The first paragraph after Description becomes intro frontmatter. if (node.children[0]?.value === 'Description' && node.children[0]?.type === 'text') { diff --git a/src/codeql-cli/tests/convert-markdown-for-docs.ts b/src/codeql-cli/tests/convert-markdown-for-docs.ts index 86b7661fc4e4..52b77be3b372 100644 --- a/src/codeql-cli/tests/convert-markdown-for-docs.ts +++ b/src/codeql-cli/tests/convert-markdown-for-docs.ts @@ -120,6 +120,15 @@ For more information, see \`codeql database analyze\`{.interpr expect(result.content).toContain('codeql database analyze') }) + test('sets intro frontmatter from the Description section', async () => { + const result = await convertContentToDocs(testContent, {}, 'bqrs-interpret.md') + + expect(result.data).toHaveProperty('intro') + expect(result.data.intro).toBe( + 'A command that interprets a single BQRS file according to the provided\nmetadata and generates output in the specified format.', + ) + }) + test('returns proper data structure', async () => { const result = await convertContentToDocs(testContent, {}, 'bqrs-interpret.md') From 89b544566fefba6df073ca268f0c7e76ce18a786 Mon Sep 17 00:00:00 2001 From: Kevin Heis Date: Thu, 1 Oct 2026 17:02:51 +0000 Subject: [PATCH 13/26] Tighten code comments in footer and sidebar components (#63419) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 7c52b57f-2c29-4aa1-8233-99d0d6e13571 --- .../components/page-footer/Contribution.tsx | 4 +- .../page-footer/DocsFooter.module.scss | 83 ++++++------------ .../components/page-footer/DocsFooter.tsx | 36 +++----- .../page-footer/FooterDivider.module.scss | 18 ++-- .../components/page-footer/FooterDivider.tsx | 22 ++--- src/frame/components/page-footer/Support.tsx | 16 ++-- .../page-footer/SupportSection.module.scss | 87 ++++++------------- .../components/page-footer/SupportSection.tsx | 8 +- .../sidebar/AllProductsLink.module.scss | 6 +- .../sidebar/SidebarCollapseContext.tsx | 43 +++------ .../components/sidebar/SidebarNav.module.scss | 70 +++++---------- src/frame/components/sidebar/SidebarNav.tsx | 37 ++------ 12 files changed, 137 insertions(+), 293 deletions(-) diff --git a/src/frame/components/page-footer/Contribution.tsx b/src/frame/components/page-footer/Contribution.tsx index 0468a0dfd5f8..f6e74a49e653 100644 --- a/src/frame/components/page-footer/Contribution.tsx +++ b/src/frame/components/page-footer/Contribution.tsx @@ -9,9 +9,7 @@ export const Contribution = () => { ? `https://github.com/github/docs/blob/main/content/${relativePath}` : 'https://github.com/github/docs' - // Heading and body styling comes from the footer column rules in - // SupportSection.module.scss. The Docs 2026 design renders these as plain body - // text rather than a bold heading plus muted copy. + // SupportSection.module.scss restyles this heading and body for the plain Docs 2026 treatment. return (

    {t`title`}

    diff --git a/src/frame/components/page-footer/DocsFooter.module.scss b/src/frame/components/page-footer/DocsFooter.module.scss index 9fbc4431cb9f..7fa3b337efaa 100644 --- a/src/frame/components/page-footer/DocsFooter.module.scss +++ b/src/frame/components/page-footer/DocsFooter.module.scss @@ -1,28 +1,15 @@ .docsFooter { - // Brand defaults the footer to canvas-subtle (a grey panel); the design puts it on - // the page canvas so it reads as continuous with the article and the band above. - // Brand's canvas, not Primer's: every other painted element in here is brand, and - // `--bgColor-default` is undefined in this app so the old value fell through to - // Primer's blue-tinted #0d1117 in dark mode. + // The design keeps the footer on the page canvas so it reads as continuous with + // the article and band above. Use Brand's canvas token because --bgColor-default + // is undefined here and falls through to Primer's blue-tinted dark canvas. --brand-footer-bg-color: var(--brand-color-canvas-default); - // The design draws all four footer controls (Yes, No, Make a contribution and - // Back to top) on brand's subtle canvas with a subtle border (#f2f5f3 on #d2d9d4, - // which is exactly what these brand tokens resolve to). - // - // Yes/No and the contribution CTA are Primer `.btn`s, so set the variables they - // read rather than overriding the rules: the survey's selected states apply - // `color-bg-success-emphasis` / `color-bg-danger-emphasis`, which set - // background-color directly and must keep winning over the rest state. - // - // BOTH spellings are required. @primer/css declares the button like this: - // border-color: var(--button-default-borderColor-rest, var(--color-btn-border)); - // The legacy `--color-btn-*` names are only the FALLBACK. They used to win because - // the `--button-default-*` names were undefined in this app, but index.scss now - // imports the Primer Primitives functional themes, which define them — so setting - // only the legacy names would silently lose to Primer's palette (#3d444d borders - // in dark). Setting the modern names is what actually takes effect; the legacy - // ones are kept so this still works if the functional themes are ever dropped. + // The design gives every footer control Brand's subtle canvas and border. + // Yes/No and Contribution are Primer buttons, so set their variables instead of + // overriding rules. Survey selected states set background-color directly and must win. + // Primer's modern contract reads --button-default-borderColor-rest before + // --color-btn-border. Set both modern and legacy names so these controls match + // Brand under either button token contract. --button-default-bgColor-rest: var(--brand-color-canvas-subtle); --button-default-borderColor-rest: var(--brand-color-border-subtle); --button-default-fgColor-rest: var(--brand-color-text-default); @@ -30,49 +17,40 @@ --color-btn-border: var(--brand-color-border-subtle); --color-btn-text: var(--brand-color-text-default); - // Brand gives Back to top a near-transparent fill by default; the design gives it - // the same treatment as the other three. Its border already matches. + // Brand gives Back to top a near-transparent fill, but the design matches the other controls. --brand-footer-backToTop-bgColor-rest: var(--brand-color-canvas-subtle); // The band above supplies the visual break, and the design puts a 2px emphasized - // rule directly beneath it, on the footer's own top edge. Brand's default here is - // a much brighter green (scale-green-3), so this stays an explicit override. + // rule directly beneath it, on the footer's own top edge. Brand's default uses a + // much brighter scale-green-3, so this stays an explicit override. border-top: 2px solid var(--brand-color-text-emphasized); - // Brand's section padding is roughly double the design's. Targeted through markers - // we own rather than brand's hashed module classes, which change between releases. - // - // Top bar: 76px in the design, sized by the 44px back-to-top control plus 16px. + // The design uses smaller section padding than Brand. Target selectors we own + // because Brand's hashed module classes change between releases. + // The top bar reaches the 76px design height with 16px around the 44px Back to top control. > div:has(.backToTop) > section { padding-top: var(--base-size-16); padding-bottom: var(--base-size-16); } - // Help region: the columns own their padding, so the section adds none. Without - // this the columns sit 32px below the rule that introduces them, and because the - // rules between rows are column borders, those rules would stop short of the - // footer edges instead of spanning it as the design shows. The inline inset moves - // onto the columns in SupportSection.module.scss so content stays aligned with the - // top and bottom bars. + // The help columns in SupportSection.module.scss own the inline padding, so row + // rules can span edge to edge while content stays aligned. > div:has(.centerSlot) > section { padding: 0; } - // Bottom bar: 76px in the design around a single 18px line. + // The bottom bar reaches the 76px design height with 28px around one line. > div:has(.bottomRow) > section { padding-top: var(--base-size-28); padding-bottom: var(--base-size-28); } - // `copyrightStatement` renders inside a brand that is a flex item of - // the bottom row. Let it fill the row so the copyright and links can sit at - // opposite ends, and drop the uppercasing the component applies to the whole

    - // (the design uppercases only the copyright, not the links). + // copyrightStatement renders inside Brand Text as p, a flex item of the bottom + // row. Let it fill the row, and remove the uppercasing Brand applies to the whole + // paragraph because the design uppercases only the copyright. p:has(> .bottomRow) { flex: 1; - // Inert while the bottom row is horizontal (flex-basis wins), but needed at - // narrow, where brand's bottom row turns into a column with align-items: - // flex-start and would otherwise shrink this to its content width. + // At narrow widths Brand stacks the bottom row, so force full width. width: 100%; min-width: 0; text-transform: none; @@ -92,10 +70,8 @@ text-transform: uppercase; } -// Narrow: the design stacks the legal links above the copyright, and gives each its -// own band separated by a rule that spans the footer. So the bottom section hands its -// padding to the two rows, the same move the help region makes, which lets the rule -// between them reach the edges while both rows stay inset and aligned with each other. +// At narrow widths, each bottom row owns its padding so the separator spans the +// footer while row content stays aligned. @media (max-width: 767px) { .docsFooter > div:has(.bottomRow) > section { padding-block: 0; @@ -137,9 +113,7 @@ font-size: 14px; font-weight: 500; text-transform: none; - // Brand's link token, not Primer's accent blue. There is no - // `--brand-color-text-link`; the real token is `-rest`, and naming it wrong meant - // this quietly fell back to Primer (#0969da) instead of brand (#0055d5). + // Use --brand-color-text-link-rest because --brand-color-text-link does not exist. color: var(--brand-color-text-link-rest); &:hover { @@ -158,11 +132,10 @@ background-color: var(--brand-color-border-subtle); } - // KO law requires the privacy statement link to be conspicuous. + // Korean law requires the privacy statement link to be conspicuous. &[data-conspicuous="true"] { - // Brand ships no attention token, so this chains to Primer's — but the old name - // is `--color-attention-fg`; spelled `--color-fg-attention` nothing in the chain - // resolved and the hardcoded light amber won in dark mode too. + // Brand ships no attention token, so chain to Primer's --color-attention-fg. + // The --color-fg-attention spelling does not exist. color: var(--fgColor-attention, var(--color-attention-fg)); font-weight: 600; } diff --git a/src/frame/components/page-footer/DocsFooter.tsx b/src/frame/components/page-footer/DocsFooter.tsx index 484c4c22ec7c..13a9941b59d1 100644 --- a/src/frame/components/page-footer/DocsFooter.tsx +++ b/src/frame/components/page-footer/DocsFooter.tsx @@ -9,19 +9,12 @@ import { useTranslation } from '@/languages/components/useTranslation' import styles from './DocsFooter.module.scss' -// The Docs 2026 site footer (Figma node 123-6013): decorative band, then brand -// MinimalFooter supplying the logomark + back-to-top row, the help region, and the -// legal/copyright strip. -// -// The design puts the legal links in the *bottom* row beside the copyright. -// MinimalFooter.Link children render in the top row instead, and the two rows live -// in separate DOM subtrees so no amount of CSS moves one into the other. Passing the -// links through `copyrightStatement`, which accepts a ReactElement and renders in -// the bottom row, gets the designed layout without overriding brand internals. -// It also sidesteps the component's hard cap of five links. -// -// Note `copyrightStatement` is rendered inside a , so everything here -// must be phrasing content: spans and anchors only, no lists or

    } > - {/* Footnotes drops any child that isn't a brand , so the machine - translation notice has to be wrapped rather than passed as a bare

    . */} + {/* Footnotes keeps only Brand Text children, so wrap the machine translation notice. */} {router.locale !== 'en' && ( {t('machine')} )} - {/* `ghd-scroll-to-top` is the global analytics hook that - src/events/components/events.ts listens for; it used to live on the - ScrollButton this control replaces, and without it activations go - untracked. BackToTop merges className, so both survive. */}

    at every width and fight the UA's content hiding (which -// modern Chrome exposes via ::details-content and older browsers via the children), -// we render the plain heading + list on the server and swap to a disclosure once we -// know the viewport is narrow. That keeps SSR and the first client render identical, -// and leaves the links reachable when JavaScript never runs. +// The Docs 2026 design groups Expert services and Blog with help destinations. +// Below the two-column breakpoint, swap the server-rendered plain list to a +// disclosure only after client media matching, so hydration matches and links stay +// reachable without JavaScript. export const Support = () => { const { t } = useTranslation('support') const { t: tFooter } = useTranslation('footer') @@ -24,7 +18,7 @@ export const Support = () => { useEffect(() => { if (typeof window === 'undefined' || !window.matchMedia) return - // Mirrors the first grid breakpoint in SupportSection.module.scss. + // Keep this query in sync with the first grid breakpoint in SupportSection.module.scss. const mql = window.matchMedia('(max-width: 767px)') const handle = (event: MediaQueryListEvent | MediaQueryList) => setIsNarrow(event.matches) handle(mql) diff --git a/src/frame/components/page-footer/SupportSection.module.scss b/src/frame/components/page-footer/SupportSection.module.scss index a90dc5727628..218329b23b77 100644 --- a/src/frame/components/page-footer/SupportSection.module.scss +++ b/src/frame/components/page-footer/SupportSection.module.scss @@ -1,17 +1,8 @@ -// The footer help region: feedback, contribution, and support links. This lives in -// MinimalFooter's `centerComponent` slot, so it supplies its own column chrome -// (24px padding and rules) rather than the page container it used to sit in. -// -// The design reflows in three stages rather than simply narrowing three columns: -// -// wide (>=1280) feedback | contribution | support -// medium (>=768) feedback across the full width, with Yes/No pushed right; -// contribution | support beneath it -// narrow (<768) everything stacked, support collapsed into a disclosure -// -// MinimalFooter already draws a rule above this whole region, so the first row must -// not add one: two rules a hair apart read as a mistake. Only rows after the first -// carry a top rule. +// MinimalFooter's centerComponent slot gives this region no page container, so the +// columns supply their own padding and rules. The design reflows by breakpoint: +// one column below 768px, a full-width survey row at 768px to 1279px, and three +// columns at 1280px and above. MinimalFooter already draws the region's top rule, +// so only later rows draw top rules. $footer-rule: 1px solid var(--brand-color-border-subtle); @@ -21,21 +12,15 @@ $footer-rule: 1px solid var(--brand-color-border-subtle); } .column { - // Vertical padding is the design's 24px. The inline inset instead mirrors - // MinimalFooter's own container padding (20px, 32px from 48rem up), because that - // container's padding was dropped for this section so the row rules, which are - // column borders, span the footer edge to edge. Keeping the same values here - // leaves column content aligned with the top and bottom bars. + // Use the design's vertical padding and mirror MinimalFooter's inline padding so + // row rules span edge to edge while column content stays aligned. padding-block: var(--base-size-24); padding-inline: var(--base-size-20); - // Brand's text colour rather than the page's Primer one. Links and buttons inside - // set their own, so this only reaches the headings and body copy. + // Use Brand's text color for headings and body copy; links and buttons set their own. color: var(--brand-color-text-default); - // Survey and Contribution each render their own

    . The design shows these as - // plain body text, not bold headings. Restyled here rather than in those - // components so they stay free of footer-specific classes. Element selectors also - // out-specify the Primer utility classes they carry. + // The design treats Survey and Contribution headings as body text. Restyle them + // here so those components stay free of footer-specific classes. h3 { margin: 0 0 var(--base-size-16); font-size: 16px; @@ -43,7 +28,7 @@ $footer-rule: 1px solid var(--brand-color-border-subtle); line-height: 1.6; } - // Contribution's intro paragraph: same plain treatment, full column width. + // Match the Contribution intro to the plain full-width treatment. :global(.contribution) p { margin: 0 0 var(--base-size-16); max-width: none; @@ -57,19 +42,16 @@ $footer-rule: 1px solid var(--brand-color-border-subtle); } } -// Stacked: every column after the first starts a new row and needs a separator. -// Scoped to the narrow range so it cannot out-specify the per-column rules that the -// wider layouts set (`.column + .column` would otherwise beat `.contributionColumn`). +// Below 768px, every column after the first starts a row and needs a separator. +// Scope this range so .column + .column cannot out-specify wider per-column rules. @media (max-width: 767px) { .column + .column { border-top: $footer-rule; } } -// Below the 3-column layout the feedback question and its Yes/No buttons sit on one -// full-width line, question left and buttons right. The survey form grows a comment -// box, email field and actions once voted, so anything that isn't the question or -// the vote buttons is pushed onto its own line. +// Below the three-column layout, keep the feedback question and vote buttons on one +// line. Follow-up fields from a completed vote need their own full-width line. @media (max-width: 1279px) { .surveyColumn form { display: flex; @@ -93,8 +75,7 @@ $footer-rule: 1px solid var(--brand-color-border-subtle); } } -// Medium: feedback spans the full width on its own row, contribution and support -// share the row beneath it. +// At 768px to 1279px, feedback spans its own row while contribution and support share the next. @media (min-width: 768px) and (max-width: 1279px) { .supportGrid { grid-template-columns: minmax(0, 1fr) max-content; @@ -104,45 +85,36 @@ $footer-rule: 1px solid var(--brand-color-border-subtle); grid-column: 1 / -1; } - // A second-row rule only makes sense when there is a first row above it. Any of - // these columns can be absent: site-policy and deprecated pages drop the survey, - // non-English drops the contribution CTA. Without this gate the surviving - // columns would draw a rule directly beneath MinimalFooter's own separator. + // Draw the second-row rule only when a survey row exists above it. Otherwise a + // surviving column would draw directly beneath MinimalFooter's separator. .supportGrid:has(.surveyColumn) .column:not(.surveyColumn) { border-top: $footer-rule; } - // Divider between the two columns that share a row, keyed to position rather than - // to which component it is, so it lands correctly whichever columns render. + // Key the shared-row divider to position so it lands correctly whichever columns render. .column:not(.surveyColumn) + .column { border-left: $footer-rule; } } -// Wide: all three side by side. Feedback is capped, contribution takes the slack, -// and the support links hug their content against the right edge. Nothing wraps, so -// no column carries a top rule. +// At 1280px and above, keep all three columns on one row with no top rules. @media (min-width: 1280px) { .supportGrid { grid-template-columns: minmax(0, 300px) minmax(0, 1fr) max-content; } - // The design draws a single vertical rule, after the first column. Keyed to - // position: `.contributionColumn` would put the rule against the region's left - // edge on pages that render no survey. + // Key the single vertical rule to position so pages without a survey avoid a left-edge rule. .column:nth-child(2) { border-left: $footer-rule; } } -// Matches the eyebrow treatment in JourneyTrackNav.module.scss. Kept local because -// CSS modules are scoped; worth extracting to a shared partial if a third use lands. -// Scoped under .column so it out-specifies the plain-heading rule above. +// Match the JourneyTrackNav eyebrow treatment. Keep it local because CSS modules +// are scoped, and nest under .column so it out-specifies the plain-heading rule. .column .eyebrow { margin: 0; font-family: var(--brand-fontStack-monospace, "Mona Sans Mono", monospace); - // Rendered as an

    to keep the column's heading semantics, so the browser's - // bold/large heading defaults have to be reset back to the eyebrow treatment. + // Reset browser heading defaults while keeping h3 semantics for the column. font-size: 12px; font-weight: 400; line-height: 1.5; @@ -154,22 +126,19 @@ $footer-rule: 1px solid var(--brand-color-border-subtle); .supportLinks { display: flex; flex-direction: column; - // The design sets a ~22px baseline rhythm for this list. Reached with a tight gap - // plus a modest line-height rather than the design's literal 1.0 leading, which - // would resist user text-spacing overrides. + // Reach the design's approximate 22px baseline rhythm without resisting user + // text-spacing overrides. gap: var(--base-size-6); margin-top: var(--base-size-16); a { line-height: 1.15; - // Without this these inherit the page's Primer link colour (#0969da) rather - // than brand's (#0055d5), which is what the design specifies. + // Use Brand blue instead of the page's Primer link color. color: var(--brand-color-text-link-rest); } } -// Narrow-only disclosure. The summary keeps its heading, shown in sentence case -// rather than the eyebrow treatment used when the list is always visible. +// At narrow widths, keep the disclosure heading in sentence case. .supportSummary { display: flex; align-items: center; diff --git a/src/frame/components/page-footer/SupportSection.tsx b/src/frame/components/page-footer/SupportSection.tsx index 7d2366d9794b..76277ef48968 100644 --- a/src/frame/components/page-footer/SupportSection.tsx +++ b/src/frame/components/page-footer/SupportSection.tsx @@ -10,8 +10,8 @@ import { useTranslation } from '@/languages/components/useTranslation' import styles from './SupportSection.module.scss' -// Renders inside MinimalFooter's `centerComponent` slot, so it no longer owns a page -// container or a section heading. The footer supplies that chrome. +// MinimalFooter's centerComponent slot supplies no page container or visible heading, +// so this region owns its column chrome. // // Columns carry their own class rather than relying on nth-child, because any of the // three can be hidden (site-policy pages drop the survey, non-English drops the @@ -35,9 +35,7 @@ export const SupportSection = () => { return ( <> - {/* The design shows no heading over this region, but dropping it entirely - leaves the three column headings with nothing above them for heading - navigation. Kept for assistive tech only. */} + {/* Keep this hidden h2 so heading navigation has a parent for the column headings. */}

    {t('support_heading')}

    {showSurvey && ( diff --git a/src/frame/components/sidebar/AllProductsLink.module.scss b/src/frame/components/sidebar/AllProductsLink.module.scss index 99cc5d9a7d1b..5fe3dae0b682 100644 --- a/src/frame/components/sidebar/AllProductsLink.module.scss +++ b/src/frame/components/sidebar/AllProductsLink.module.scss @@ -1,7 +1,5 @@ -// The "back to home" link. Replaces primer/css's `Link--primary` and -// `color-fg-default`, which both paint Primer's #e6edf3 in dark mode rather than -// brand's #ffffff; both carry `!important`, so the classes have to go rather than be -// overridden. +// Replace Primer Link--primary and color-fg-default because both use !important and +// paint Primer's dark-mode foreground instead of Brand white. .allProductsLink { color: var(--brand-color-text-default); } diff --git a/src/frame/components/sidebar/SidebarCollapseContext.tsx b/src/frame/components/sidebar/SidebarCollapseContext.tsx index 36a9ffedab43..acaa7b4ac766 100644 --- a/src/frame/components/sidebar/SidebarCollapseContext.tsx +++ b/src/frame/components/sidebar/SidebarCollapseContext.tsx @@ -5,18 +5,12 @@ import { useRouter } from 'next/router' import Cookies from '@/frame/components/lib/cookies' import { SIDEBAR_COLLAPSED_COOKIE_NAME } from '@/frame/lib/constants' -// Persists whether the desktop doc-tree rail is collapsed, and holds the -// (non-persisted) open state of the inline mobile nav. Mirrors the per-branch -// expand persistence in src/landings/components/useSidebarExpandState.tsx: -// collapsed state is kept in a cookie and shared through context so the -// secondary bar's toggle, the layout that renders the rail, and the mobile nav -// trigger all stay in sync. -// -// SSR-safety: the cookie is read server-side in getMainContext and passed to the -// provider as `initialCollapsed`, so the first render (server + client hydration) -// already reflects the persisted state and markup matches, with no flash of the -// open rail before it collapses. When no initial is supplied, it falls back to reading -// the cookie client-side via the SSR-safe cookie lib. +// Keep two sidebar state channels separate. collapsed persists in a cookie and +// syncs the desktop toggle with the rail layout, mirroring +// src/landings/components/useSidebarExpandState.tsx. mobileNavOpen never persists; +// the mobile trigger reads it for inline nav expansion. getMainContext reads the +// cookie server-side and passes initialCollapsed, so server markup and hydration +// match. Without an initial value, the client falls back to the SSR-safe cookie helper. function readCollapsed(): boolean { try { @@ -30,18 +24,16 @@ function persistCollapsed(collapsed: boolean) { try { Cookies.set(SIDEBAR_COLLAPSED_COOKIE_NAME, String(collapsed)) } catch { - // Cookie writes may fail (disabled cookies, etc.), so degrade to non-persisted - // state rather than throwing. + // Disabled cookies must degrade to non-persisted state instead of throwing. } } type SidebarCollapseContextValue = { - // Desktop: whether the left rail is collapsed (persisted). + // Desktop rail collapse persists in a cookie. collapsed: boolean toggleCollapsed: () => void setCollapsed: (collapsed: boolean) => void - // Mobile: whether the doc-tree nav is expanded inline (not persisted). The - // nav renders in the page flow, same as desktop, not in a dialog overlay. + // Mobile inline nav state never persists, and it stays in page flow rather than a dialog. mobileNavOpen: boolean toggleMobileNav: () => void closeMobileNav: () => void @@ -57,8 +49,7 @@ export function SidebarCollapseProvider({ initialCollapsed?: boolean }) { const { asPath } = useRouter() - // Seed from the SSR-read cookie value so server and first client render agree. - // When no initial is supplied, fall back to reading the cookie client-side. + // Prefer the server-read cookie value so server markup and hydration match. const [collapsed, setCollapsedState] = useState(() => initialCollapsed ?? readCollapsed()) const [mobileNavOpen, setMobileNavOpen] = useState(false) @@ -78,21 +69,19 @@ export function SidebarCollapseProvider({ const toggleMobileNav = useCallback(() => setMobileNavOpen((prev) => !prev), []) const closeMobileNav = useCallback(() => setMobileNavOpen(false), []) - // Client-side navigation doesn't unmount the inline mobile nav, so close it - // when the route (or REST in-page hash) changes. + // Close the mounted inline mobile nav across client-side route and REST hash changes. useEffect(() => { setMobileNavOpen(false) }, [asPath]) - // Close the inline nav when the desktop rail takes over. Keep 1012px aligned - // with SidebarNav's lg breakpoint and DefaultLayout's content visibility. + // Keep 1012px aligned with SidebarNav's lg breakpoint and DefaultLayout's content visibility. useEffect(() => { if (typeof window === 'undefined' || !window.matchMedia) return const mql = window.matchMedia('(min-width: 1012px)') const handle = (e: MediaQueryListEvent | MediaQueryList) => { if (e.matches) setMobileNavOpen(false) } - handle(mql) // close immediately if already at/above lg on mount + handle(mql) mql.addEventListener('change', handle) return () => mql.removeEventListener('change', handle) }, []) @@ -112,11 +101,7 @@ export function SidebarCollapseProvider({ return {children} } -/** - * Read/toggle the desktop rail's collapsed state and the inline mobile nav's - * open state. Falls back to a no-op expanded/closed state if used outside the - * provider. - */ +// Read and toggle sidebar state. Outside the provider, return no-op expanded and closed state. export function useSidebarCollapsed(): SidebarCollapseContextValue { const ctx = useContext(SidebarCollapseContext) if (!ctx) { diff --git a/src/frame/components/sidebar/SidebarNav.module.scss b/src/frame/components/sidebar/SidebarNav.module.scss index bcf72088bd98..c761ec6e929c 100644 --- a/src/frame/components/sidebar/SidebarNav.module.scss +++ b/src/frame/components/sidebar/SidebarNav.module.scss @@ -5,23 +5,17 @@ .sidebarFull { @include breakpoint(lg) { - // Fixed width for consistent sidebar layout + // Keep the desktop rail width fixed so article layout stays stable. width: 326px; - // Sticky stack = header + Docs 2026 secondary bar (45px). Match `top` to the - // stack exactly so the rail doesn't jump 1px when the sticky positioning - // engages on scroll. The header contributes --docs-header-height rather than - // a literal 65px: the Brand header is 3.5rem below 48rem and 4rem above, so - // a fixed px stack would be wrong at one of the two sizes. + // Match top to the header plus 45px secondary bar so sticky activation does + // not jump. Use --docs-header-height because Brand header height changes. height: calc(100vh - var(--docs-header-height) - 45px); top: calc(var(--docs-header-height) + 45px); } } -// The