diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 0a0100fcb2ebb50..6b8a900f49a23a7 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -63,14 +63,14 @@ .azure-pipelines/ @AA-Turner # GitHub & related scripts -.github/ @ezio-melotti @hugovk @AA-Turner @webknjaz @itamaro @JacobCoffee -Tools/build/compute-changes.py @AA-Turner @hugovk @webknjaz -Lib/test/test_tools/test_compute_changes.py @AA-Turner @hugovk @webknjaz +.github/ @ezio-melotti @hugovk @AA-Turner @webknjaz @itamaro @JacobCoffee @StanFromIreland +Tools/build/compute-changes.py @AA-Turner @hugovk @webknjaz @StanFromIreland +Lib/test/test_tools/test_compute_changes.py @AA-Turner @hugovk @webknjaz @StanFromIreland Tools/build/verify_ensurepip_wheels.py @AA-Turner @pfmoore @pradyunsg # Pre-commit -.pre-commit-config.yaml @hugovk @JacobCoffee -.ruff.toml @hugovk @AlexWaygood @AA-Turner @JacobCoffee +.pre-commit-config.yaml @hugovk @JacobCoffee @StanFromIreland +.ruff.toml @hugovk @AlexWaygood @AA-Turner @JacobCoffee @StanFromIreland # Patchcheck Tools/patchcheck/ @AA-Turner @itamaro diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 177615621f6b8c7..61e4e04ba272dc8 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -16,7 +16,7 @@ body: Make sure to also search the [CPython issue tracker](https://github.com/python/cpython/issues?q=is%3Aissue+sort%3Acreated-desc) to check that the bug has not already been reported. - type: textarea attributes: - label: "Bug description:" + label: "Bug report" description: > Give a clear and concise description of what happened. Include a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) if possible. @@ -34,7 +34,6 @@ body: label: "CPython versions tested on:" multiple: true options: - - "3.10" - "3.11" - "3.12" - "3.13" diff --git a/.github/ISSUE_TEMPLATE/crash.yml b/.github/ISSUE_TEMPLATE/crash.yml index 81ae91e5b0af973..973d4c38528f3e6 100644 --- a/.github/ISSUE_TEMPLATE/crash.yml +++ b/.github/ISSUE_TEMPLATE/crash.yml @@ -10,8 +10,9 @@ body: The CPython interpreter is written in a different programming language, C. A "CPython crash" is when Python itself fails, leading to a traceback in the C stack. - type: textarea attributes: - label: What happened? + label: Crash report description: > + Give a clear and concise description of what happened. Include a [minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example) if possible. [Copy and paste code where possible rather than using screenshots](https://meta.stackoverflow.com/a/285557/13990016), and put any code blocks inside triple backticks. @@ -27,7 +28,6 @@ body: label: "CPython versions tested on:" multiple: true options: - - "3.10" - "3.11" - "3.12" - "3.13" diff --git a/.github/ISSUE_TEMPLATE/feature.yml b/.github/ISSUE_TEMPLATE/feature.yml index 4361ab2bf827fb6..3ecae7112170e0a 100644 --- a/.github/ISSUE_TEMPLATE/feature.yml +++ b/.github/ISSUE_TEMPLATE/feature.yml @@ -12,7 +12,7 @@ body: Major feature proposals should generally be discussed on [Discourse](https://discuss.python.org/c/ideas/6) before opening a GitHub issue. Wait until it's clear that most people support your idea before filling in this form. - type: textarea attributes: - label: "Proposal:" + label: "Feature or enhancement" description: > Explain your proposal, why it should be implemented, and how it would be used. Add examples, if applicable. diff --git a/.github/VULNERABILITY_REPORT.yml b/.github/VULNERABILITY_REPORT.yml new file mode 100644 index 000000000000000..2887c7092d19fe1 --- /dev/null +++ b/.github/VULNERABILITY_REPORT.yml @@ -0,0 +1,73 @@ +name: Vulnerability report +description: Privately report a potential security vulnerability in CPython +body: + - type: markdown + attributes: + value: | + **Not all bugs are vulnerabilities.** Before submitting, read the [Python security policy](https://devguide.python.org/security/policy/) to understand which issues are vulnerabilities and what versions of Python accept reports. + + Keep the report short and in plain text: no headers, tables, PDFs, binaries, or severity and CVSS information. + + Reports that do not contain a potential security vulnerability will be discarded without a reply. + + To report vulnerabilities that affect other projects (such as pip or python.org), or if you are not sure where to send your report, email [security@python.org](mailto:security@python.org). + - type: textarea + id: summary + attributes: + label: Summary + description: A few sentences describing the vulnerability. + validations: + required: true + - type: textarea + id: threat_model + attributes: + label: Threat model + description: > + What does the attacker control, and what do they gain? Describe the code, configuration, or deployment that may exist in the real world and is exploitable. Where possible, cite the relevant part of the [security policy](https://devguide.python.org/security/policy/#what-types-of-bugs-are-vulnerabilities). + validations: + required: true + - type: textarea + id: proof_of_concept + attributes: + label: Proof of concept + description: > + A script that reproduces the issue and clearly indicates whether the vulnerability is present, such as exiting with `1` if vulnerable and `0` if not. If it depends on a specially constructed binary file, include a script to construct the file rather than the file itself. + + Wrap scripts longer than a few lines in a [collapsed section](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/organizing-information-with-collapsed-sections) using `
...
`. + validations: + required: true + - type: dropdown + id: versions + attributes: + label: "CPython versions tested on:" + description: > + If any tested version was not vulnerable, say which in the summary. Only [supported versions](https://devguide.python.org/versions/) accept reports. + multiple: true + options: + - "3.11" + - "3.12" + - "3.13" + - "3.14" + - "3.15" + - "3.16" + - "CPython main branch" + validations: + required: true + - type: textarea + id: patch + attributes: + label: Suggested fix + description: Ideally, a minimal patch with the mitigation. + validations: + required: false + - type: checkboxes + id: checklist + attributes: + label: Before submitting + options: + - label: I have read the security policy and evaluated this report against it. + required: true + - label: I have checked that this issue is not already resolved on the `main` branch. + required: true + - label: I have verified the factual validity of everything in this report, including any content produced by an LLM. + required: true diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 5f9e3c323e30b82..0d367e01bca224f 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -28,6 +28,17 @@ updates: cooldown: default-days: 14 + - package-ecosystem: "npm" + directory: "/Platforms/emscripten/browser_test/" + schedule: + interval: "quarterly" + labels: ["OS-emscripten", "skip issue", "skip news"] + groups: + npm: + patterns: ["*"] + cooldown: + default-days: 14 + # Release branches: Dependabot only reads this file from the default # branch, so each branch that should get its own actions bumps needs an # entry here with `target-branch`. Add one when a new release branch is @@ -92,18 +103,6 @@ updates: cooldown: default-days: 14 - - package-ecosystem: "github-actions" - target-branch: "3.10" - directory: "/" - schedule: - interval: "quarterly" - labels: ["skip issue", "skip news"] - groups: - actions: - patterns: ["*"] - cooldown: - default-days: 14 - # Only bump bugfix branches for pip. Remove # the entry when branch goes security-only. - package-ecosystem: "pip" @@ -130,14 +129,28 @@ updates: cooldown: default-days: 14 - - package-ecosystem: "pip" - target-branch: "3.13" - directory: "/Tools/" + # Only bump bugfix branches for npm. Remove + # the entry when branch goes security-only. + - package-ecosystem: "npm" + target-branch: "3.15" + directory: "/Platforms/emscripten/browser_test/" schedule: interval: "quarterly" - labels: ["skip issue", "skip news"] + labels: ["OS-emscripten", "skip issue", "skip news"] groups: - pip: + npm: + patterns: ["*"] + cooldown: + default-days: 14 + + - package-ecosystem: "npm" + target-branch: "3.14" + directory: "/Platforms/emscripten/browser_test/" + schedule: + interval: "quarterly" + labels: ["OS-emscripten", "skip issue", "skip news"] + groups: + npm: patterns: ["*"] cooldown: default-days: 14 diff --git a/.github/workflows/add-issue-header.yml b/.github/workflows/add-issue-header.yml deleted file mode 100644 index 55095f17bb4cce9..000000000000000 --- a/.github/workflows/add-issue-header.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: Add issue header -# Automatically edits an issue's descriptions with a header, -# one of: -# -# - Bug report -# - Crash report -# - Feature or enhancement - -on: - issues: - types: - # Only ever run once - - opened - -permissions: - contents: read - -jobs: - add-header: - runs-on: ubuntu-slim - permissions: - issues: write - timeout-minutes: 5 - steps: - - uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 - with: - # language=JavaScript - script: | - // https://devguide.python.org/triage/labels/#type-labels - const HEADERS = new Map([ - ['type-bug', 'Bug report'], - ['type-crash', 'Crash report'], - ['type-feature', 'Feature or enhancement'], - ]); - let issue_data = await github.rest.issues.get({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo - }).then(issue => issue.data); - let header = ''; - for (const label_data of issue_data.labels) { - const label_name = (typeof label_data === 'string') ? label_data : label_data.name; - if (HEADERS.has(label_name)) { - header = HEADERS.get(label_name); - break; - } - } - if (header !== '') { - console.log(`Setting new header: ${header}`); - await github.rest.issues.update({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: `# ${header}\n\n${issue_data.body.replaceAll('\r', '')}` - }); - } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 90686b83d4a6369..07087b836cb597e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -346,18 +346,28 @@ jobs: run: python3 Platforms/Android ci --fast-ci ${{ matrix.arch }}-linux-android build-ios: - name: iOS + name: >- + iOS + ${{ fromJSON(matrix.free-threading) && '(free-threading)' || '' }} needs: build-context if: needs.build-context.outputs.run-ios == 'true' timeout-minutes: 60 runs-on: macos-26 + strategy: + fail-fast: false + matrix: + free-threading: + - false + - true steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Build and test - run: python3 Platforms/Apple ci iOS --fast-ci + run: >- + python3 Platforms/Apple ci iOS --fast-ci + ${{ fromJSON(matrix.free-threading) && '--abiflags t' || '' }} build-emscripten: name: 'Emscripten' @@ -481,53 +491,6 @@ jobs: if: fromJSON(needs.build-context.outputs.run-tests) uses: ./.github/workflows/reusable-test-lazy-imports-all.yml - build-asan: - name: 'Address sanitizer' - runs-on: ${{ matrix.os }} - timeout-minutes: 60 - needs: build-context - if: needs.build-context.outputs.run-ubuntu == 'true' - strategy: - fail-fast: false - matrix: - os: [ubuntu-26.04] - env: - OPENSSL_VER: 3.5.9 - PYTHONSTRICTEXTENSIONBUILD: 1 - ASAN_OPTIONS: detect_leaks=0:allocator_may_return_null=1:handle_segv=0 - steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 - with: - persist-credentials: false - - name: Runner image version - run: echo "IMAGE_OS_VERSION=${ImageOS}-${ImageVersion}" >> "$GITHUB_ENV" - - name: Register gcc problem matcher - run: echo "::add-matcher::.github/problem-matchers/gcc.json" - - name: Install dependencies - run: sudo ./.github/workflows/posix-deps-apt.sh - - name: Configure OpenSSL env vars - run: | - echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" - echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}" >> "$GITHUB_ENV" - echo "LD_LIBRARY_PATH=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}/lib" >> "$GITHUB_ENV" - - name: 'Restore OpenSSL build' - id: cache-openssl - uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 - with: - path: ./multissl/openssl/${{ env.OPENSSL_VER }} - key: ${{ matrix.os }}-multissl-openssl-${{ env.OPENSSL_VER }} - - name: Install OpenSSL - if: steps.cache-openssl.outputs.cache-hit != 'true' - run: python3 Tools/ssl/multissltests.py --steps=library --base-directory "$MULTISSL_DIR" --openssl "$OPENSSL_VER" --system Linux - - name: Configure CPython - run: ./configure --config-cache --with-address-sanitizer --without-pymalloc --with-openssl="$OPENSSL_DIR" - - name: Build CPython - run: make -j4 - - name: Display build info - run: make pythoninfo - - name: Tests - run: xvfb-run make ci - build-san: # ${{ '' } is a hack to nest jobs under the same sidebar category. name: Sanitizers${{ '' }} # zizmor: ignore[obfuscation] @@ -544,9 +507,15 @@ jobs: sanitizer: - TSan include: + - check-name: Address + sanitizer: ASan + free-threading: false - check-name: Undefined behavior sanitizer: UBSan free-threading: false + - check-name: Memory + sanitizer: MSan + free-threading: false uses: ./.github/workflows/reusable-san.yml with: sanitizer: ${{ matrix.sanitizer }} @@ -586,7 +555,7 @@ jobs: - name: Display build info run: | "$BUILD_DIR/cross-python/bin/python3" -m test.pythoninfo - - name: Run test subset with host build + - name: Run test subset with cross build run: | "$BUILD_DIR/cross-python/bin/python3" -m test test_sysconfig test_site test_embed @@ -654,7 +623,6 @@ jobs: - build-wasi - test-hypothesis - test-lazy-imports-all - - build-asan - build-san - cross-build-linux - cifuzz @@ -701,7 +669,6 @@ jobs: build-ubuntu-installed, build-ubuntu-ssltests, test-hypothesis, - build-asan, build-san, cross-build-linux, ' diff --git a/.github/workflows/mypy.yml b/.github/workflows/mypy.yml index 2650e7a8116804e..a8c95627c2417de 100644 --- a/.github/workflows/mypy.yml +++ b/.github/workflows/mypy.yml @@ -69,7 +69,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0 + - uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0 with: python-version: "3.15" activate-environment: true diff --git a/.github/workflows/reusable-cifuzz.yml b/.github/workflows/reusable-cifuzz.yml index 4a05396f3463e9f..a70b75eebc4a989 100644 --- a/.github/workflows/reusable-cifuzz.yml +++ b/.github/workflows/reusable-cifuzz.yml @@ -43,7 +43,7 @@ jobs: path: ./out/artifacts - name: Upload SARIF if: always() && steps.build.outcome == 'success' - uses: github/codeql-action/upload-sarif@b96794f015dfd88f77b49b1c93e0fa7110f94c63 # v4.38.0 + uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2 with: sarif_file: cifuzz-sarif/results.sarif checkout_path: cifuzz-sarif diff --git a/.github/workflows/reusable-san.yml b/.github/workflows/reusable-san.yml index 6b4a43b259638e2..9c31241498a75e9 100644 --- a/.github/workflows/reusable-san.yml +++ b/.github/workflows/reusable-san.yml @@ -43,6 +43,15 @@ jobs: # please update the versions in `Tools/pixi-packages/variants.yaml` too. echo "CC=clang" >> "$GITHUB_ENV" echo "CXX=clang++" >> "$GITHUB_ENV" + - name: ASan option setup + if: inputs.sanitizer == 'ASan' + run: | + echo "MULTISSL_DIR=${GITHUB_WORKSPACE}/multissl" >> "$GITHUB_ENV" + echo "OPENSSL_DIR=${GITHUB_WORKSPACE}/multissl/openssl/${OPENSSL_VER}" >> "$GITHUB_ENV" + echo "ASAN_OPTIONS=${SAN_LOG_OPTION} detect_leaks=0 allocator_may_return_null=1 handle_segv=0" >> "$GITHUB_ENV" + echo "PYTHONSTRICTEXTENSIONBUILD=1" >> "$GITHUB_ENV" + env: + SAN_LOG_OPTION: log_path=${{ github.workspace }}/san_log - name: TSan option setup if: inputs.sanitizer == 'TSan' run: | @@ -60,7 +69,7 @@ jobs: || '' }} - name: UBSan option setup - if: inputs.sanitizer != 'TSan' + if: inputs.sanitizer == 'UBSan' run: >- echo "UBSAN_OPTIONS=${SAN_LOG_OPTION} @@ -69,19 +78,38 @@ jobs: >> "$GITHUB_ENV" env: SAN_LOG_OPTION: log_path=${{ github.workspace }}/san_log + - name: MSan option setup + if: inputs.sanitizer == 'MSan' + run: | + sudo sysctl -w vm.mmap_rnd_bits=28 # Reduce ASLR to avoid MSan re-executing + + echo "MSAN_OPTIONS=${SAN_LOG_OPTION} allocator_may_return_null=1 handle_segv=0" >> "$GITHUB_ENV" + # MSan reports false positives for memory initialized by libraries + # that are not built with MSan, so disable modules that use them. + # _remote_debugging links to libzstd directly, but we unpoision the memory. + { + echo '*disabled*' + echo '_bz2 _ctypes _curses _curses_panel _dbm _decimal _gdbm _hashlib' + echo '_lzma _sqlite3 _ssl _tkinter _uuid _zstd readline zlib' + } > Modules/Setup.local + env: + SAN_LOG_OPTION: log_path=${{ github.workspace }}/san_log - name: Add ccache to PATH run: | echo "PATH=/usr/lib/ccache:$PATH" >> "$GITHUB_ENV" - - name: 'Restore OpenSSL build (TSan)' + - name: 'Restore OpenSSL build' id: cache-openssl - if: inputs.sanitizer == 'TSan' + if: inputs.sanitizer == 'TSan' || inputs.sanitizer == 'ASan' uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: path: ./multissl/openssl/${{ env.OPENSSL_VER }} - key: ${{ env.IMAGE_OS_VERSION }}-multissl-openssl-tsan-${{ env.OPENSSL_VER }} - - name: Install OpenSSL (TSan) + key: >- + ${{ env.IMAGE_OS_VERSION }}-multissl-openssl-${{ + inputs.sanitizer == 'TSan' && 'tsan-' || '' + }}${{ env.OPENSSL_VER }} + - name: Install OpenSSL if: >- - inputs.sanitizer == 'TSan' + (inputs.sanitizer == 'TSan' || inputs.sanitizer == 'ASan') && steps.cache-openssl.outputs.cache-hit != 'true' run: >- python3 Tools/ssl/multissltests.py @@ -89,18 +117,32 @@ jobs: --base-directory="${MULTISSL_DIR}" --openssl="${OPENSSL_VER}" --system=Linux - --tsan + ${{ inputs.sanitizer == 'TSan' && '--tsan' || '' }} + # gh-157958: -O2 instead of the pydebug default -Og to avoid a clang 21 + # compile-time blowup on some interpreter files. + # (https://github.com/llvm/llvm-project/issues/179695) + # MSan uses --with-assertions instead of --with-pydebug because its + # hooks on the Python memory allocators hide uninitialized reads. - name: Configure CPython run: >- ./configure --config-cache + OPT="-O2 -g" ${{ - inputs.sanitizer == 'TSan' + inputs.sanitizer == 'ASan' + && '--with-address-sanitizer --without-pymalloc' + || inputs.sanitizer == 'TSan' && '--with-thread-sanitizer' + || inputs.sanitizer == 'MSan' + && '--with-memory-sanitizer' || '--with-undefined-behavior-sanitizer --with-strict-overflow' }} - --with-pydebug - ${{ inputs.sanitizer == 'TSan' && '--with-openssl="$OPENSSL_DIR" --with-openssl-rpath=auto' || '' }} + ${{ inputs.sanitizer == 'MSan' && '--with-assertions' || '--with-pydebug' }} + ${{ + (inputs.sanitizer == 'TSan' || inputs.sanitizer == 'ASan') + && '--with-openssl="$OPENSSL_DIR" --with-openssl-rpath=auto' + || '' + }} ${{ inputs.free-threading && '--disable-gil' || '' }} - name: Build CPython run: make -j4 diff --git a/.gitignore b/.gitignore index 00813f9d1b6b00a..2fce2ac10ae9a74 100644 --- a/.gitignore +++ b/.gitignore @@ -76,10 +76,10 @@ Lib/test/data/* Apple/iOS/Frameworks/ Apple/iOS/Resources/Info.plist Apple/testbed/build -Apple/testbed/Python.xcframework/*/bin -Apple/testbed/Python.xcframework/*/include -Apple/testbed/Python.xcframework/*/lib -Apple/testbed/Python.xcframework/*/Python.framework +Platforms/Apple/testbed/Python*.xcframework/*/bin +Platforms/Apple/testbed/Python*.xcframework/*/include +Platforms/Apple/testbed/Python*.xcframework/*/lib +Platforms/Apple/testbed/Python*.xcframework/*/Python*.framework Apple/testbed/*Testbed.xcodeproj/project.xcworkspace Apple/testbed/*Testbed.xcodeproj/xcuserdata Mac/Makefile diff --git a/Doc/Makefile b/Doc/Makefile index 881719394b63662..0d5fd41627ff292 100644 --- a/Doc/Makefile +++ b/Doc/Makefile @@ -189,12 +189,13 @@ venv: .PHONY: lock lock: # Dependencies have a 14 day cooldown period to mitigate supply chain attacks, -# except for sphinx_linklint and python-docs-theme, which are maintained by +# except for blurb, python-docs-theme and sphinx_linklint, which are maintained by # core team members. $(UV) pip compile requirements.txt \ --upgrade --exclude-newer P14D \ - --exclude-newer-package sphinx_linklint=PT0S \ + --exclude-newer-package blurb=PT0S \ --exclude-newer-package python-docs-theme=PT0S \ + --exclude-newer-package sphinx_linklint=PT0S \ --no-cache --output-file $(REQUIREMENTS) \ --python-version 3.12 --universal \ --custom-compile-command="make lock" diff --git a/Doc/_static/tachyon-example-flamegraph.html b/Doc/_static/tachyon-example-flamegraph.html index 701c959bda3ccc0..0563e7ce4acfe6e 100644 --- a/Doc/_static/tachyon-example-flamegraph.html +++ b/Doc/_static/tachyon-example-flamegraph.html @@ -1,5 +1,5 @@ - + @@ -166,6 +166,14 @@ /* Heatmap span highlighting colors */ --span-hot-base: 255, 100, 50; --span-cold-base: 150, 150, 150; + + /* Summary card colors - optimized for 4.5:1 contrast on light bg */ + --card-blue: 44, 102, 149; + --card-green: 26, 116, 49; + --card-yellow: 134, 100, 4; + --card-purple: 102, 57, 166; + --card-red: 180, 40, 50; + --card-orange: 166, 90, 0; } /* Dark theme */ @@ -190,15 +198,15 @@ --header-gradient: linear-gradient(135deg, #21262d 0%, #30363d 100%); - /* Dark mode heat palette - muted colors that provide sufficient contrast with light text */ - --heat-1: rgba(74, 123, 167, 0.35); - --heat-2: rgba(90, 159, 168, 0.38); - --heat-3: rgba(106, 181, 181, 0.40); - --heat-4: rgba(126, 196, 136, 0.42); - --heat-5: rgba(160, 216, 120, 0.45); - --heat-6: rgba(196, 222, 106, 0.48); - --heat-7: rgba(244, 212, 77, 0.50); - --heat-8: rgba(255, 107, 53, 0.55); + /* Dark mode heat palette - cool to warm gradient for visualization */ + --heat-1: rgba(90, 123, 167, 1); + --heat-2: rgba(106, 148, 168, 1); + --heat-3: rgba(122, 172, 172, 1); + --heat-4: rgba(142, 196, 152, 1); + --heat-5: rgba(168, 216, 136, 1); + --heat-6: rgba(200, 222, 122, 1); + --heat-7: rgba(244, 212, 93, 1); + --heat-8: rgba(255, 122, 69, 1); /* Code view specific - dark mode */ --code-bg: #0d1117; @@ -228,6 +236,14 @@ /* Heatmap span highlighting colors - dark theme */ --span-hot-base: 255, 107, 53; --span-cold-base: 189, 189, 189; + + /* Summary card colors - optimized for 4.5:1 contrast on dark bg */ + --card-blue: 88, 166, 255; + --card-green: 63, 185, 80; + --card-yellow: 210, 153, 34; + --card-purple: 163, 113, 247; + --card-red: 248, 113, 113; + --card-orange: 251, 146, 60; } /* -------------------------------------------------------------------------- @@ -571,6 +587,46 @@ This file extends the shared foundation with flamegraph-specific styles. ========================================================================== */ +/* -------------------------------------------------------------------------- + Differential Flamegraph + -------------------------------------------------------------------------- */ + +:root { + /* Regression colors */ + --diff-regression-deep: #d32f2f; + --diff-regression-medium: #e57373; + --diff-regression-light: #ef9a9a; + --diff-regression-verylight: #ffcdd2; + + /* Improvement colors */ + --diff-improvement-deep: #1976d2; + --diff-improvement-medium: #42a5f5; + --diff-improvement-light: #64b5f6; + --diff-improvement-verylight: #90caf9; + + /* Other differential colors */ + --diff-neutral: #bdbdbd; + --diff-new: #9575cd; + --diff-elided: #b39ddb; +} + +/* Dark mode differential colors - adjusted for contrast against dark backgrounds */ +[data-theme="dark"] { + --diff-regression-deep: #ef5350; + --diff-regression-medium: #e57373; + --diff-regression-light: #ef9a9a; + --diff-regression-verylight: #ffcdd2; + + --diff-improvement-deep: #42a5f5; + --diff-improvement-medium: #64b5f6; + --diff-improvement-light: #90caf9; + --diff-improvement-verylight: #bbdefb; + + --diff-neutral: #757575; + --diff-new: #b39ddb; + --diff-elided: #ce93d8; +} + /* -------------------------------------------------------------------------- Layout Overrides (Flamegraph-specific) -------------------------------------------------------------------------- */ @@ -841,9 +897,17 @@ } /* View Mode Section */ +.view-mode-section { + display: flex; + flex-direction: column; + gap: 8px; +} + .view-mode-section .section-content { display: flex; - justify-content: center; + flex-direction: column; + gap: 10px; + align-items: center; } /* Collapsible sections */ @@ -912,10 +976,10 @@ position: relative; } -.summary-card:nth-child(1) { --i: 0; --card-color: 55, 118, 171; } -.summary-card:nth-child(2) { --i: 1; --card-color: 40, 167, 69; } -.summary-card:nth-child(3) { --i: 2; --card-color: 255, 193, 7; } -.summary-card:nth-child(4) { --i: 3; --card-color: 111, 66, 193; } +.summary-card:nth-child(1) { --i: 0; --card-color: var(--card-blue); } +.summary-card:nth-child(2) { --i: 1; --card-color: var(--card-green); } +.summary-card:nth-child(3) { --i: 2; --card-color: var(--card-yellow); } +.summary-card:nth-child(4) { --i: 3; --card-color: var(--card-purple); } .summary-card:hover { border-color: rgba(var(--card-color), 0.6); @@ -971,21 +1035,18 @@ text-overflow: ellipsis; } -/* Efficiency Bar */ -.efficiency-section { - margin-top: 10px; - padding-top: 10px; - border-top: 1px solid var(--border); -} +/* -------------------------------------------------------------------------- + Progress Bars + -------------------------------------------------------------------------- */ -.efficiency-header { +.bar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; } -.efficiency-label { +.bar-label { font-size: 9px; font-weight: 600; color: var(--text-secondary); @@ -993,21 +1054,21 @@ letter-spacing: 0.2px; } -.efficiency-value { +.bar-value { font-family: var(--font-mono); font-size: 11px; font-weight: 700; color: var(--accent); } -.efficiency-bar { +.bar { height: 6px; background: var(--bg-tertiary); border-radius: 3px; overflow: hidden; } -.efficiency-fill { +.bar-fill { height: 100%; background: linear-gradient(90deg, #28a745 0%, #20c997 50%, #17a2b8 100%); border-radius: 3px; @@ -1016,7 +1077,7 @@ overflow: hidden; } -.efficiency-fill::after { +.bar-fill::after { content: ''; position: absolute; top: 0; @@ -1033,68 +1094,56 @@ } /* -------------------------------------------------------------------------- - Thread Stats Grid (in Sidebar) + Efficiency Section Container + -------------------------------------------------------------------------- */ + +.efficiency-section { + margin-top: 10px; + padding-top: 10px; + border-top: 1px solid var(--border); +} + +/* -------------------------------------------------------------------------- + Thread Stats Progress Bars (in Sidebar) -------------------------------------------------------------------------- */ .thread-stats-section { display: block; } -.stats-grid { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 8px; +.stats-container { + display: flex; + flex-direction: column; + gap: 10px; } -.stat-tile { - background: var(--bg-primary); - border-radius: 8px; - padding: 10px; - text-align: center; - border: 2px solid var(--border); - transition: all var(--transition-fast); +.stat-item { animation: fadeIn 0.4s ease-out backwards; animation-delay: calc(var(--i, 0) * 0.05s); } -.stat-tile:nth-child(1) { --i: 0; } -.stat-tile:nth-child(2) { --i: 1; } -.stat-tile:nth-child(3) { --i: 2; } -.stat-tile:nth-child(4) { --i: 3; } +.stat-item:nth-child(1) { --i: 0; } +.stat-item:nth-child(2) { --i: 1; } +.stat-item:nth-child(3) { --i: 2; } +.stat-item:nth-child(4) { --i: 3; } +.stat-item:nth-child(5) { --i: 4; } -.stat-tile:hover { - transform: translateY(-2px); - box-shadow: var(--shadow-sm); +/* Color variants for bar-fill */ +.bar-fill--green { + background: linear-gradient(90deg, #28a745 0%, #20c997 100%); } -.stat-tile-value { - font-family: var(--font-mono); - font-size: 16px; - font-weight: 700; - color: var(--text-primary); - line-height: 1.2; +.bar-fill--yellow { + background: linear-gradient(90deg, #ffc107 0%, #ffdb4d 100%); } -.stat-tile-label { - font-size: 9px; - font-weight: 600; - text-transform: uppercase; - letter-spacing: 0.3px; - color: var(--text-muted); - margin-top: 2px; +.bar-fill--purple { + background: linear-gradient(90deg, #6f42c1 0%, #9b6dd6 100%); } -/* Stat tile color variants */ -.stat-tile--green { --tile-color: 40, 167, 69; --tile-text: #28a745; } -.stat-tile--red { --tile-color: 220, 53, 69; --tile-text: #dc3545; } -.stat-tile--yellow { --tile-color: 255, 193, 7; --tile-text: #d39e00; } -.stat-tile--purple { --tile-color: 111, 66, 193; --tile-text: #6f42c1; } - -.stat-tile[class*="--"] { - border-color: rgba(var(--tile-color), 0.4); - background: linear-gradient(135deg, rgba(var(--tile-color), 0.08) 0%, var(--bg-primary) 100%); +.bar-fill--red { + background: linear-gradient(90deg, #dc3545 0%, #ff6b7a 100%); } -.stat-tile[class*="--"] .stat-tile-value { color: var(--tile-text); } /* -------------------------------------------------------------------------- Hotspot Cards @@ -1396,6 +1445,41 @@ color: var(--accent); } +.tooltip-diff { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border); +} + +.tooltip-diff-title { + font-size: 11px; + font-weight: 600; + color: var(--accent); + margin-bottom: 8px; +} + +.tooltip-diff-row { + display: grid; + grid-template-columns: auto 1fr; + gap: 4px 14px; + font-size: 12px; + margin-bottom: 4px; +} + +.tooltip-diff-row.regression .tooltip-stat-value { + color: var(--diff-regression-deep); + font-weight: 700; +} + +.tooltip-diff-row.improvement .tooltip-stat-value { + color: var(--diff-improvement-deep); + font-weight: 700; +} + +.tooltip-diff-row.neutral .tooltip-stat-value { + color: var(--text-secondary); +} + .tooltip-source { margin-top: 10px; padding-top: 10px; @@ -1551,10 +1635,6 @@ .brand-info { display: none; } - - .stats-grid { - grid-template-columns: 1fr; - } } /* -------------------------------------------------------------------------- @@ -1574,7 +1654,9 @@ Flamegraph-Specific Toggle Override -------------------------------------------------------------------------- */ -#toggle-invert .toggle-track.on { +#toggle-invert .toggle-track.on, +#toggle-elided .toggle-track.on, +#toggle-path-display .toggle-track.on { background: #8e44ad; border-color: #8e44ad; box-shadow: 0 0 8px rgba(142, 68, 173, 0.3); @@ -1674,7 +1756,7 @@